Created
November 8, 2022 00:05
-
-
Save HansMuller/e255cf93b91688aa33e74688a15e1042 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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