Skip to content

Instantly share code, notes, and snippets.

@HansMuller
Created November 8, 2022 00:05
Show Gist options
  • Save HansMuller/e255cf93b91688aa33e74688a15e1042 to your computer and use it in GitHub Desktop.
Save HansMuller/e255cf93b91688aa33e74688a15e1042 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
enum GlideDirection {
startToEnd,
endToStart,
up,
down,
}
// animation.value == 0, child is not visible
// animation.value == 1, child is visible
class Glide extends StatefulWidget {
const Glide({ super.key, required this.direction, required this.animation, this.child });
final GlideDirection direction;
final Animation<double> animation;
final Widget? child;
@override
State<Glide> createState() => _GlideState();
}
class _GlideState extends State<Glide> {
late final Animation<Offset> offsetAnimation;
late final Animation<double> sizeAnimation;
@override
void didChangeDependencies() {
super.didChangeDependencies();
late final Offset beginOffset;
final bool ltr = Directionality.of(context) == TextDirection.ltr;
switch (widget.direction) {
case GlideDirection.startToEnd:
beginOffset = ltr ? const Offset(-1, 0) : const Offset(1, 0);
break;
case GlideDirection.endToStart:
beginOffset = ltr ? const Offset(1, 0) : const Offset(-1, 0);
break;
case GlideDirection.down:
beginOffset = const Offset(0, -1);
break;
case GlideDirection.up:
beginOffset = const Offset(0, 1);
break;
}
offsetAnimation = Tween<Offset>(
begin: beginOffset,
end: Offset.zero,
).animate(
CurvedAnimation(
parent: widget.animation,
curve: const Interval(
0.25, 1.0,
curve: Curves.easeOut,
),
),
);
sizeAnimation = Tween<double>(
begin: 0,
end: 1,
).animate(
CurvedAnimation(
parent: widget.animation,
curve: const Interval(
0.0, 0.75,
curve: Curves.easeOut,
),
),
);
}
// TBD didUpdateWidget, handle widget.animation changing
@override
Widget build(BuildContext context) {
final bool isVertical = widget.direction == GlideDirection.up || widget.direction == GlideDirection.down;
return ClipRect(
child: Align(
alignment: Alignment.topLeft,
widthFactor: isVertical ? null : sizeAnimation.value,
heightFactor: isVertical ? sizeAnimation.value : null,
child: FractionalTranslation(
translation: offsetAnimation.value,
child: widget.child,
),
),
);
}
}
class _Destination {
const _Destination(this.icon, this.label);
final IconData icon;
final String label;
}
class Home extends StatefulWidget {
const Home({ super.key });
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
late final AnimationController controller;
int selectedIndex = 0;
@override initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
final List<_Destination> destinations = const <_Destination>[
_Destination(Icons.home, 'Home'),
_Destination(Icons.business, 'Business'),
_Destination(Icons.school, 'School'),
];
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (BuildContext context, Widget? child) {
return Scaffold(
body: Row(
children: <Widget>[
Glide(
animation: controller,
direction: GlideDirection.startToEnd,
child: NavigationRail(
selectedIndex: selectedIndex,
onDestinationSelected: (int index) {
setState(() {
selectedIndex = index;
});
},
destinations: destinations.map<NavigationRailDestination>((_Destination d) {
return NavigationRailDestination(
icon: Icon(d.icon),
label: Text(d.label),
);
}).toList(),
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.secondaryContainer,
alignment: Alignment.center,
child: ElevatedButton(
onPressed: () {
if (controller.isCompleted) {
controller.reverse();
} else {
controller.forward();
}
},
child: const Text('Animate'),
),
),
),
],
),
bottomNavigationBar: Glide(
direction: GlideDirection.up,
animation: ReverseAnimation(controller),
child: BottomNavigationBar(
items: destinations.map<BottomNavigationBarItem>((_Destination d) {
return BottomNavigationBarItem(
icon: Icon(d.icon),
label: d.label,
);
}).toList(),
currentIndex: selectedIndex,
onTap: (int index) {
setState(() {
selectedIndex = index;
});
},
),
),
);
}
);
}
}
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const Home(),
),
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment