Skip to content

Instantly share code, notes, and snippets.

@HansMuller
Created November 16, 2022 17:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HansMuller/3535e63e2dd3ace38dc33fd3256e4c70 to your computer and use it in GitHub Desktop.
Save HansMuller/3535e63e2dd3ace38dc33fd3256e4c70 to your computer and use it in GitHub Desktop.
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