Created
November 11, 2022 04:23
-
-
Save HansMuller/4898280997f357d7db94cdcafa3e27b9 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'; | |
| class RailTransition extends StatefulWidget { | |
| const RailTransition({ super.key, required this.animation, required this.child }); | |
| final Animation<double> animation; | |
| final Widget child; | |
| @override | |
| State<RailTransition> createState() => _RailTransition(); | |
| } | |
| class _RailTransition extends State<RailTransition> { | |
| late final Animation<Offset> offsetAnimation; | |
| late final Animation<double> sizeAnimation; | |
| @override | |
| void didChangeDependencies() { | |
| super.didChangeDependencies(); | |
| // The animations are only rebuilt by this method when the text | |
| // direction changes because this widget only depends on Directionality. | |
| final bool ltr = Directionality.of(context) == TextDirection.ltr; | |
| offsetAnimation = Tween<Offset>( | |
| begin: ltr ? const Offset(-1, 0) : const Offset(1, 0), | |
| 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, | |
| ), | |
| ), | |
| ); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return ClipRect( | |
| child: Align( | |
| alignment: Alignment.topLeft, | |
| widthFactor: sizeAnimation.value, | |
| child: FractionalTranslation( | |
| translation: offsetAnimation.value, | |
| child: widget.child, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class BarTransition extends StatefulWidget { | |
| const BarTransition({ super.key, required this.animation, required this.child }); | |
| final Animation<double> animation; | |
| final Widget child; | |
| @override | |
| State<BarTransition> createState() => _BarTransition(); | |
| } | |
| class _BarTransition extends State<BarTransition> { | |
| late final Animation<Offset> offsetAnimation; | |
| late final Animation<double> sizeAnimation; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| offsetAnimation = Tween<Offset>( | |
| begin: const Offset(0, 1), | |
| 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, | |
| ), | |
| ), | |
| ); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return ClipRect( | |
| child: Align( | |
| alignment: Alignment.topLeft, | |
| heightFactor: sizeAnimation.value, | |
| child: FractionalTranslation( | |
| translation: offsetAnimation.value, | |
| child: widget.child, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class AnimatedFloatingActionButton extends StatefulWidget { | |
| const AnimatedFloatingActionButton({ super.key, required this.animation, this.onPressed, this.child }); | |
| final Animation<double> animation; | |
| final VoidCallback? onPressed; | |
| final Widget? child; | |
| @override | |
| State<AnimatedFloatingActionButton> createState() => _AnimatedFloatingActionButton(); | |
| } | |
| class _AnimatedFloatingActionButton extends State<AnimatedFloatingActionButton> { | |
| late final Animation<ShapeBorder?> shapeAnimation; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| shapeAnimation = ShapeBorderTween( | |
| begin: const CircleBorder(), | |
| end: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))), | |
| ).animate( | |
| CurvedAnimation( | |
| parent: widget.animation, | |
| curve: Curves.easeOut, | |
| ), | |
| ); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return ScaleTransition( | |
| scale: widget.animation, | |
| child: FloatingActionButton( | |
| onPressed: widget.onPressed, | |
| shape: shapeAnimation.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; | |
| late final Animation<double> railAnimation; | |
| late final Animation<double> barAnimation; | |
| late final Animation<double> fabAnimation; | |
| int selectedIndex = 0; | |
| bool controllerInitialized = false; | |
| @override initState() { | |
| super.initState(); | |
| controller = AnimationController( | |
| duration: const Duration(milliseconds: 600), | |
| value: 0, | |
| vsync: this, | |
| ); | |
| // Transition Orchestration | |
| railAnimation = CurvedAnimation( | |
| parent: controller, | |
| curve: const Interval(0.5, 1.0), | |
| ); | |
| barAnimation = CurvedAnimation( | |
| parent: controller, | |
| curve: const Interval(0.0, 0.5), | |
| ); | |
| fabAnimation = CurvedAnimation( | |
| parent: controller, | |
| curve: const Interval(0.25, 0.5), | |
| ); | |
| } | |
| @override | |
| void dispose() { | |
| controller.dispose(); | |
| super.dispose(); | |
| } | |
| @override | |
| void didChangeDependencies() { | |
| super.didChangeDependencies(); | |
| final double width = MediaQuery.of(context).size.width; | |
| if (width < 500) { | |
| controller.forward(); | |
| } else { | |
| controller.reverse(); | |
| } | |
| if (!controllerInitialized) { | |
| controllerInitialized = true; | |
| controller.value = width < 500 ? 1 : 0; | |
| } | |
| } | |
| 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>[ | |
| RailTransition( | |
| animation: railAnimation, | |
| child: NavigationRail( | |
| selectedIndex: selectedIndex, | |
| onDestinationSelected: (int index) { | |
| setState(() { | |
| selectedIndex = index; | |
| }); | |
| }, | |
| leading: Column( | |
| children: <Widget>[ | |
| IconButton( | |
| onPressed: () { }, | |
| icon: const Icon(Icons.menu), | |
| ), | |
| const SizedBox(height: 8), | |
| FloatingActionButton( | |
| onPressed: () { }, | |
| child: const Icon(Icons.add), | |
| ), | |
| ], | |
| ), | |
| groupAlignment: -0.35, | |
| 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.surfaceVariant, | |
| alignment: Alignment.center, | |
| ), | |
| ), | |
| ], | |
| ), | |
| floatingActionButton: AnimatedFloatingActionButton( | |
| animation: ReverseAnimation(fabAnimation), | |
| onPressed: () { }, | |
| child: const Icon(Icons.add), | |
| ), | |
| bottomNavigationBar: BarTransition( | |
| animation: ReverseAnimation(barAnimation), | |
| 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