Created
November 16, 2022 17:23
-
-
Save HansMuller/3535e63e2dd3ace38dc33fd3256e4c70 to your computer and use it in GitHub Desktop.
This file contains 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'; | |
import 'package:flutter/scheduler.dart' show timeDilation; | |
const double transitionLength = 1250; | |
class SizeAnimation extends CurvedAnimation { | |
SizeAnimation(Animation<double> parent) : super( | |
parent: parent, | |
curve: const Interval( | |
250 / transitionLength, 1000 / transitionLength, | |
curve: Curves.easeInOutCubicEmphasized, | |
), | |
reverseCurve: Interval( | |
0, 250 / transitionLength, | |
curve: Curves.easeInOutCubicEmphasized.flipped, | |
), | |
); | |
} | |
class OffsetAnimation extends CurvedAnimation { | |
OffsetAnimation(Animation<double> parent) : super( | |
parent: parent, | |
curve: const Interval( | |
500 / transitionLength, 1.0, | |
curve: Curves.easeInOutCubicEmphasized, | |
), | |
reverseCurve: Interval( | |
0, 250 / transitionLength, | |
curve: Curves.easeInOutCubicEmphasized.flipped, | |
), | |
); | |
} | |
class RailTransition extends StatefulWidget { | |
const RailTransition({ super.key, required this.animation, required this.backgroundColor, required this.child }); | |
final Animation<double> animation; | |
final Widget child; | |
final Color backgroundColor; | |
@override | |
State<RailTransition> createState() => _RailTransition(); | |
} | |
class _RailTransition extends State<RailTransition> { | |
late Animation<Offset> offsetAnimation; | |
late Animation<double> widthAnimation; | |
@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; | |
widthAnimation = Tween<double>( | |
begin: 0, | |
end: 1, | |
).animate(SizeAnimation(widget.animation)); | |
offsetAnimation = Tween<Offset>( | |
begin: ltr ? const Offset(-1, 0) : const Offset(1, 0), | |
end: Offset.zero, | |
).animate(OffsetAnimation(widget.animation)); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return ClipRect( | |
child: DecoratedBox( | |
decoration: BoxDecoration(color: widget.backgroundColor), | |
child: Align( | |
alignment: Alignment.topLeft, | |
widthFactor: widthAnimation.value, | |
child: FractionalTranslation( | |
translation: offsetAnimation.value, | |
child: widget.child, | |
), | |
), | |
), | |
); | |
} | |
} | |
class BarTransition extends StatefulWidget { | |
const BarTransition({ super.key, required this.animation, required this.backgroundColor, required this.child }); | |
final Animation<double> animation; | |
final Color backgroundColor; | |
final Widget child; | |
@override | |
State<BarTransition> createState() => _BarTransition(); | |
} | |
class _BarTransition extends State<BarTransition> { | |
late final Animation<Offset> offsetAnimation; | |
late final Animation<double> heightAnimation; | |
@override | |
void initState() { | |
super.initState(); | |
offsetAnimation = Tween<Offset>( | |
begin: const Offset(0, 1), | |
end: Offset.zero, | |
).animate(OffsetAnimation(widget.animation)); | |
heightAnimation = Tween<double>( | |
begin: 0, | |
end: 1, | |
).animate(SizeAnimation(widget.animation)); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return ClipRect( | |
child: DecoratedBox( | |
decoration: BoxDecoration(color: widget.backgroundColor), | |
child: Align( | |
alignment: Alignment.topLeft, | |
heightFactor: heightAnimation.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 Animation<double> scaleAnimation; | |
late Animation<ShapeBorder?> shapeAnimation; | |
late Color backgroundColor; | |
late Color foregroundColor; | |
@override | |
void didChangeDependencies() { | |
super.didChangeDependencies(); | |
final ColorScheme colorScheme = Theme.of(context).colorScheme; | |
backgroundColor = colorScheme.tertiaryContainer; | |
foregroundColor = colorScheme.onTertiaryContainer; | |
scaleAnimation = CurvedAnimation( | |
parent: widget.animation, | |
curve: Interval( | |
750 / transitionLength, 1000 / transitionLength, | |
curve: Curves.easeInOutCubicEmphasized | |
), | |
reverseCurve: Interval( | |
0, 250 / transitionLength, | |
curve: Curves.easeInOutCubicEmphasized.flipped, | |
), | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return ScaleTransition( | |
scale: scaleAnimation, | |
child: FloatingActionButton( | |
backgroundColor: backgroundColor, | |
foregroundColor: foregroundColor, | |
onPressed: widget.onPressed, | |
child: widget.child, | |
), | |
); | |
} | |
} | |
class OneTwoTransition extends StatefulWidget { | |
const OneTwoTransition({ | |
super.key, | |
required this.animation, | |
required this.backgroundColor, | |
required this.one, | |
required this.two, | |
}); | |
final Animation<double> animation; | |
final Color backgroundColor; | |
final Widget one; | |
final Widget two; | |
@override | |
State<OneTwoTransition> createState() => _OneTwoTransitionState(); | |
} | |
class _OneTwoTransitionState extends State<OneTwoTransition> { | |
late final Animation<Offset> offsetAnimation; | |
late final Animation<double> widthAnimation; | |
@override | |
void initState() { | |
super.initState(); | |
offsetAnimation = Tween<Offset>( | |
begin: const Offset(0, 1), | |
end: Offset.zero, | |
).animate(OffsetAnimation(widget.animation)); | |
widthAnimation = Tween<double>( | |
begin: 0, | |
end: 2000, | |
).animate(SizeAnimation(widget.animation)); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Row( | |
children: <Widget>[ | |
Flexible( | |
flex: 1000, | |
child: widget.one, | |
), | |
Flexible( | |
flex: widthAnimation.value.toInt(), | |
child: FractionalTranslation( | |
translation: offsetAnimation.value, | |
child: widget.two, | |
), | |
), | |
], | |
); | |
} | |
} | |
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 railAnimation; | |
late final barAnimation; | |
int selectedIndex = 0; | |
bool controllerInitialized = false; | |
@override initState() { | |
super.initState(); | |
controller = AnimationController( | |
duration: Duration(milliseconds: transitionLength.toInt() * 2), | |
value: 0, | |
vsync: this, | |
); | |
barAnimation = ReverseAnimation( | |
CurvedAnimation( | |
parent: controller, | |
curve: const Interval(0.0, 0.5), | |
), | |
); | |
railAnimation = CurvedAnimation( | |
parent: controller, | |
curve: const Interval(0.5, 1.0), | |
); | |
} | |
@override | |
void dispose() { | |
controller.dispose(); | |
super.dispose(); | |
} | |
@override | |
void didChangeDependencies() { | |
super.didChangeDependencies(); | |
final double width = MediaQuery.of(context).size.width; | |
final AnimationStatus status = controller.status; | |
if (width > 600) { | |
if (status != AnimationStatus.forward && status != AnimationStatus.completed) { | |
controller.forward(); | |
} | |
} else { | |
if (status != AnimationStatus.reverse && status != AnimationStatus.dismissed) { | |
controller.reverse(); | |
} | |
} | |
if (!controllerInitialized) { | |
controllerInitialized = true; | |
controller.value = width > 600 ? 1 : 0; | |
} | |
} | |
final List<_Destination> destinations = const <_Destination>[ | |
_Destination(Icons.inbox_rounded, 'Inbox'), | |
_Destination(Icons.article_outlined, 'Articles'), | |
_Destination(Icons.messenger_outline_rounded, 'Messages'), | |
_Destination(Icons.group_outlined, 'Groups'), | |
]; | |
@override | |
Widget build(BuildContext context) { | |
final ColorScheme colorScheme = Theme.of(context).colorScheme; | |
final Color backgroundColor = const Color(0xffecebf4); | |
return AnimatedBuilder( | |
animation: controller, | |
builder: (BuildContext context, Widget? child) { | |
return Scaffold( | |
body: Row( | |
children: <Widget>[ | |
RailTransition( | |
animation: railAnimation, | |
backgroundColor: backgroundColor, | |
child: NavigationRail( | |
selectedIndex: selectedIndex, | |
backgroundColor: backgroundColor, | |
onDestinationSelected: (int index) { | |
setState(() { | |
selectedIndex = index; | |
}); | |
}, | |
leading: Column( | |
children: <Widget>[ | |
IconButton( | |
onPressed: () { }, | |
icon: Icon(Icons.menu), | |
), | |
SizedBox(height: 8), | |
AnimatedFloatingActionButton( | |
animation: railAnimation, | |
onPressed: () { }, | |
child: const Icon(Icons.add), | |
), | |
], | |
), | |
groupAlignment: -0.85, | |
destinations: destinations.map<NavigationRailDestination>((_Destination d) { | |
return NavigationRailDestination( | |
icon: Icon(d.icon), | |
label: Text(d.label), | |
); | |
}).toList(), | |
), | |
), | |
Expanded( | |
child: OneTwoTransition( | |
animation: railAnimation, | |
backgroundColor: backgroundColor, | |
one: Container(color: colorScheme.primaryContainer), | |
two: Container(color: colorScheme.surface), | |
), | |
), | |
], | |
), | |
floatingActionButton: AnimatedFloatingActionButton( | |
animation: barAnimation, | |
onPressed: () { }, | |
child: const Icon(Icons.add), | |
), | |
bottomNavigationBar: BarTransition( | |
animation: barAnimation, | |
backgroundColor: backgroundColor, | |
child: NavigationBar( | |
elevation: 0, | |
destinations: destinations.map<NavigationDestination>((_Destination d) { | |
return NavigationDestination( | |
icon: Icon(d.icon), | |
label: d.label, | |
); | |
}).toList(), | |
selectedIndex: selectedIndex, | |
onDestinationSelected: (int index) { | |
setState(() { | |
selectedIndex = index; | |
}); | |
}, | |
), | |
), | |
); | |
} | |
); | |
} | |
} | |
void main() { | |
timeDilation = 1; | |
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