While Flutter has an enormous amount of amazing packages for creating animation effects for your apps, there are also built-in methods for manually creating more fine-tuned animations.
For our cross-screen animations I’ll be assuming that you already know how to create basic routes, just to keep things brief. You can review the docs here if you’re not comfortable with that just yet.
The main three parts of our animations are the ticker
to control our time, the controller
to register our parameters like our duration, and then the values we want changed. Before our widget is rendered, in our initState
, we can set our controller to its parameters, set its direction, and add a listener to reset our widgets state with every change.
By default a controller will move change from 0 to 1 in the time we set for our duration, we can print our controller.value
in our listener to watch this happen. We can change our default start and end values by setting the upperBound
or lowerBound
properties.
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
AnimationController controller;
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this); // Links this controller to this widget so it won't run if the parent widget isn't rendered, to save on resources.
controller.forward();
controller.addListener(() => setState(() {}));
}
}
To use our animation we just need to set whatever we want, like opacity, to controller.value
.
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Opacity(
opacity: controller.value,
child: Container(width: 50, height: 50, color: Colors.red),
),
),
);
}
Instead of boring linear animations we can use different curved variations to control exactly how our controller changes. To do this we can user CurvedAnimation
to create a kind of wrapper over our original controller. This wrapper will take its parent, our controller, and the curve we want to apply. When using a curved animation we can’t have lower and upper bounds besides 0 and 1, so when we apply it we can just multiply its value. There’s a really extensive list of options over in the docs.
Another useful method on controller
is addStatusListener
, which will allow us to tap into its lifecycle. Our controller triggers a completed or dismissed event whenever it’s value has reached its upper or lower bound. We can use this with its forward
and reverse
methods to loop our animation infinitely.
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.slowMiddle);
controller.forward();
animation.addListener(() => setState(() {}));
controller.addStatusListener((status) {
if (status == AnimationStatus.completed) controller.reverse(from: 400);
else if (status == AnimationStatus.dismissed) controller.forward();
});
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
margin: EdgeInsets.only(bottom: animation.value * 400),
width: 50,
height: 50,
color: Colors.red),
),
);
}
}
Instead of just working with number values, we can also work with ranges of other things, like colors, using tweens (short for between). Like with our curved animation, it will act as a wrapper for our controller or animation and we can set our color to its value.
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
Animation changeColor;
void initState() {
super.initState();
controller =
AnimationController(duration: Duration(seconds: 1), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.slowMiddle);
changeColor = ColorTween(begin: Colors.red, end: Colors.blue).animate(animation);
controller.forward();
animation.addListener(() => setState(() {}));
controller.addStatusListener((status) {
if (status == AnimationStatus.completed) controller.reverse(from: 400);
else if (status == AnimationStatus.dismissed) controller.forward();
});
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
margin: EdgeInsets.only(bottom: animation.value * 400),
width: 50,
height: 50,
color: changeColor.value),
),
);
}
}
Another awesome technique is to use the Hero widget when we want a transition between widgets on different screens. We just need to wrap each in its own Hero widget with matching tags. The tags must be unique and there cannot be more than one on the screen at any time.
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SizedBox(height: 1),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Hero(tag: 'icon', child: Icon(Icons.add)),
]),
Navbar()
]));
}
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SizedBox(height: 1),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
SizedBox(width: 1),
Hero(
tag: 'icon',
child: Icon(Icons.add, color: Colors.red, size: 75)),
]),
Navbar()
]),
);
}
With most frontend web technologies from the past, animations were left as an after-thought, but the flutter team did an amazing job at keeping animations in mind when developing this amazing framework. We really only scratched the surface of what Flutter is capable of in terms of animations.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!