Flutter Animation
Apps are boring! Of course, only until you add good looking animations into them. Since Flutter renders everything to the screen itself without relying on native views, you can animate literally everything.
Every custom animation begins with an AnimationController. It takes in a Duration and generates doubles with values between 0 (beginning) and 1 (end) in the specified time span. You can kick off this 0 to 1 animation with a forward method.
There’s one additional step though — you have to tell this controller how many times per second it should progress toward the final value. For this, use a special ticker mixin on the widget containing the AnimationController. Depending on the device screen FPS, this ticker will “tick” 60 or maybe even 90 times per second. A new value will be generated on every tick.
import 'package:flutter/material.dart'; class AnimationPage extends StatefulWidget {
_AnimationPageState createState() => _AnimationPageState();
} // Use TickerProviderStateMixin if you have multiple AnimationControllers class _AnimationPageState extends State<AnimationPage>
with SingleTickerProviderStateMixin
{
AnimationController animController;
@override
void initState()
{
super.initState();
animController = AnimationController(
duration: Duration(seconds: 5),
// This takes in the TickerProvider, which is this _AnimationPageState object
vsync: this,);
// Goes from 0 to 1, we'll do something with these values later on animController.forward();
} @override
Widget build(BuildContext context)
{
...
}
@override
void dispose()
{
animController.dispose();
super.dispose();
}
}
For using the values inside a widget, you need an Animation<double> instance which exposes a value property. Types other than double are, of course, also supported.
The AnimationController class is itself a subclass of Animation. So, while you can use the 0 to 1 values directly from the controller, you usually want to modify the range of these values.
After all, what if you want values ranging from 0 to 500? And what about animating color changes from purple to green?
Changing the value range and even the type of the output value (e.g. to a Color) is possible with a Tween. There are many pre-built ones such as ColorTween or TextStyleTween.
In the app we’re building, we’re going to use a simple Tween<double>. The value range of the animation should be a full circle for the rotation. Expressed in radians, it’s 0 to 2π (0 to 360 degrees).
Tween takes in an Animation<double>, maps its progress to new values and returns a modified Animation. Store this returned Animation, in this case, Animation<double>, in a field.
class _AnimationPageState extends State<AnimationPage> with SingleTickerProviderStateMixin
{ Animation<double> animation;
AnimationController animController;
@override
void initState()
{
super.initState();
animController = AnimationController(duration: Duration(seconds: 5), vsync: this,);
animation = Tween<double>(begin: 0,end: 2 * math.pi, ).animate(animController);
animController.forward();
}
... }
By now we have the final animation going from 0 to 2*PI and we even tell the AnimationController to start animating by calling the forward method. Two things are still missing though:
- Using the animation’s value for the Transform’s rotation.
- Calling setState to rebuild the UI when the animation value changes. This is possible with a listener.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class AnimationPage extends StatefulWidget
{
_AnimationPageState createState() => _AnimationPageState();
} // Use TickerProviderStateMixin if you have multiple AnimationControllers class _AnimationPageState extends State<AnimationPage> with SingleTickerProviderStateMixin
{ Animation<double> animation;
AnimationController animController; @override
void initState()
{
super.initState();
animController = AnimationController(duration: Duration(seconds: 5), vsync: this,);
animation = Tween<double>(begin: 0,end: 2 * math.pi, ).animate(animController)
..addListener(()
{
// Empty setState because the updated value is already in the animation field
setState(() {});
});
animController.forward();
} @override
Widget build(BuildContext context)
{
return Scaffold(
body: Transform.rotate(
angle: animation.value,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(30),
child: Image.asset('assets/image.png',),
),
),
);
} @override
void dispose()
{
animController.dispose();
super.dispose();
}
}
Finally! We have a working visual animation! It’s only going in one direction — forward.
In addition to a regular listener which is updated on every newly generated value, there’s also a statusListener. An animation can be in 4 stages:
- Dismissed — stopped at the beginning
- Forward — animating in the forward direction (0 to 1)
- Completed — stopped at the end
- Reverse — animating from 1 to 0