Flutter animation concepts
- How it works
- Essential background concepts
- Animation<T> class
- AnimationController
- AnimationStatus
- AnimatedBuilder
- AnimatedContainer
- AnimatedWidget
- Curves
- Listeners and notifications
- Physics-based animation
- Scheduler
- Simulations
- Tickers
- Tweens
- vsync
This section describes the basic concepts behind the Flutter animation architecture.
If you use Flutter’s built-in implicit and transition widgets, you may not need to interact with all of the Flutter core animation concepts directly because most of the work required to code the animation is already done. But for customized explicit animations, it’s important to understand the animation concepts used to fine-tune and control your animations.
How it works
Animation is the movement of an object or a change over time—like a fade in or out, or a bounce. An animation represents a value of a specific type that can change over time—specifically over the lifetime of the animation. Most animation widgets receive an Animation
object as a parameter, from which the animation widget reads the current value of the animation and listens for changes to that value. It listens for “state” changes.
Whenever the animation’s object value changes, the animation object notifies all the listeners (that were added with addListener
). Typically, a State
object that listens to an animation will call setState
in its listener callback to notify the widget system that it needs to rebuild with the new value of the animation.
There are three Flutter widgets that help animation widgets rebuild when a state change occurs.
|
Animations also provide an AnimationStatus
enum which indicates animation status: completed, dismissed, forward, and reverse. Whenever the animation’s status changes, the animation notifies all the listeners that were added with addStatusListener
. Typically, animations start out in the dismissed status, which means they’re at the beginning of their range. For example, animations that progress from the 0.0 to 1.0 value range are dismissed when their value returns to 0.0. When the animation reaches the end of its range, the animation reaches the completed status.
The animation system in Flutter is based on typed Animation
objects. Widgets can either incorporate these animations in their build functions directly by reading their current value and listening to their state changes or they can use the animations as the basis of more elaborate animations that they pass along to other widgets.
To summarize how it works:
- Animations are objects with a time dependent value.
- The value is updated each time Flutter generates a frame.
- Animations are Listenables: they notify listeners when their value changes.
- A
StatefulWidget
listens to an animation and responds to notifications by callingsetState()
. - Flutter animations are stateful widgets whose build method depends on one or more animation values.
- The
Animation<T>
class is an abstract class.
Essential background concepts
Flutter animation is built upon basic Flutter and Dart concepts such as the StatefulWidget
and the StatelessWidget
and working with the main.dart
and pubspec.yaml
files. You can find important basic information in the following resources.
- A Tour of the Flutter Widget Framework
- Introductory codelabs, you might want to start with Write Your First Flutter App, part 1 and Write Your First flutter App, part 2.
Material Design
The Material Design motion concepts provide professional guidance to help with your animation designs so that when you add animation, it makes the UI expressive and easy to use. It’s important to make sure that your animation is informative and focused so that your animation doesn’t interfere or detract from the function of your app. The Material Design resources can help you plan for your animation as well as understand the design concepts behind animations.
When you’re planning your animation, check out the Material Design principals of motion. For example, you’ll find information about animation design concepts such as sequences or easing (acceleration over time) so that transitions don’t look stiff and mechanical. Easing is a way to adjust an animation’s rate of change—easing allows transitioning elements to speed up and slow down, rather than moving at a constant rate. In the real world, things don’t start or stop moving instantaneously. They take time to speed up and slow down. Easing makes elements move as though influenced by natural forces like friction.</p>
This graph shows the easing of an element that remains visible throughout a transition. The y-axis shows the position and the x-axis shows the time.
You can also find helpful information about using Material components on the Flutter website.
Dart language basics
Although you don’t need to be a Dart programmer to create a Flutter mobile app, you can find helpful information on the Dart programming language website and in the Dart Language Tour.
For example, Dart uses cascade (..
) notation. Method cascading is a syntax that calls multiple methods on the same object. The example below shows ..addListener()
which means that the addListener()
method is called with the return value from animate()
.
```Dart
animation = tween.animate(controller)
..addListener(() {
setState(() {
// the animation object’s value is the changed state
});
});
```
This code is equivalent to:
```Dart
animation = tween.animate(controller);
animation.addListener(() {
setState(() {
// the animation object’s value is the changed state
});
});
```
In addition to function calls, cascade notation allows you to access fields on that same object. This often saves you the step of creating a temporary variable and allows you to write more fluid code.
You can learn more about cascade notation in the Dart Language Tour.
Animation<T> class
The primary building block of Flutter animation is the Animation<T>
class. The Animation object is a core class in Flutter’s animation library that interpolates the values used to guide an animation. The Animation<T>
abstract class provides a value of a given type, a concept of the animation direction, the animation status, and a listener interface to register callbacks that are invoked when the value or status changes.
Animation objects
The animation system in Flutter is based on typed Animation objects. Widgets can either incorporate these animations in their build functions directly by reading their current value and listening to their state changes or they can use the animations as the basis of more elaborate animations that they pass along to other widgets.
An Animation object in Flutter is a class that sequentially generates interpolated numbers between two values over a certain duration. The output of an Animation object may be linear, a curve, a step function, or any other mapping you define. Depending on how the Animation object is controlled, it could run in reverse, or even switch directions in the middle of the animation.
The Animation object knows the current state of an animation, for example, whether it’s started, stopped, or moving forward or in reverse, but doesn’t know what appears onscreen.
Animation<double>
One of the more commonly used animation types is Animation<double>
which is used to represent a double value in the range of 0.0-1.0. This is the input expected by the Curve
and Tween
classes, as well as other Animation
subclasses. The Animation object has state and its current value is always available in the .value
member.
Animations can also interpolate types other than double, such as Animation<Color>
or Animation<Size>
.
Animatables
An object that can produce a value of type T
given an Animation
as input. Typically, the values of the input animation range from 0.0 to 1.0; however, any value could be provided.
The Animatable
abstract class maps a double to a value of a particular type. For example, the Tween
abstract class maps a double value ranging from 0.0-1.0 to a typed value such as a Color
or another double. It is an Animatable
.
Passing an Animatable<double>
(the parent) to an Animatable
chain()
method creates a new Animatable
subclass that applies the parent’s mapping and then the child’s mapping.
Passing an Animation<double>
(the new parent) to an Animatable
animate()
method creates a new Animation
subclass that acts like the Animatable
but is driven from the given parent.
Animation subclasses
Some Animation
subclasses are stateless (they forward listeners to their parents) and some Animation
subclasses are stateful. Examples of Animation
subclasses that have values that never change are kAlwaysCompleteAnimation
, kAlwaysDismissedAnimation
, or AlwaysStoppedAnimation
; so registering callbacks on these subclasses have no effect as the callbacks are never called.
Composable animations
Most Animation
subclasses take an explicit “parent” Animation<double>
and they are driven by that parent.
-
The
CurvedAnimation
subclass takes anAnimation<double>
class (the parent) and a couple of Curve classes (the forward and reverse curves) as input. This subclass uses the value of the parent as input to the curves to determine its output.CurvedAnimation
is immutable and stateless. -
The
ReverseAnimation
subclass takes anAnimation<double>
class as its parent and reverses all the values of the animation. It assumes the parent is using a value nominally in the range 0.0-1.0 and returns a value in the range 1.0-0.0. The status and direction of the parent animation are also reversed. ReverseAnimation is immutable and stateless. -
The
ProxyAnimation
subclass takes anAnimation<double>
class as its parent and forwards the current state of that parent. The parent is mutable. -
The
TrainHoppingAnimation
subclass takes two parents, and switches between them when their values cross.
AnimationController
The AnimationController
manages the animation and it’s the main child class of the Animation<T>
class. The AnimationController
class controls the Animation<T>
object and lets you perform tasks such as the following:
- Play an animation forward or in reverse.
- Start and stop an animation.
- Set the animation to a specific value.
- Define the upperBound and lowerBound values of an animation.
- Create a fling animation effect using a physics simulation.
For explicit animations, you build an AnimationController
. For implicit and transition animations, the AnimationController
is already defined and provided for you in the Flutter widgets so you only need to provide a few parameters such as duration and curve.
AnimationController
is an Animation
object that can be used wherever an Animation
object is needed. It is derived from Animation<double>
, and by default, it linearly produces values that range from 0.0 to 1.0 during a specified duration. A new value is produced whenever the hardware is ready for a new frame. The value generation is tied to the screen refresh, so typically 60 numbers are generated per second. After each value is generated, each Animation object calls the attached Listener
objects.
The AnimationController
has methods that are used to control the animation. For example, an animation can start with the .forward()
method.
The AnimationController
constructor includes the following arguments. The TickerProvider
and vsync
are required and can’t be null.
AnimationController({
double value,
Duration duration,
String debugLabel,
double lowerBound: 0.0,
double upperBound: 1.0,
@required TickerProvider vsync
})
- The
value
is the initial value of the animation. If defaults to the lower bound. - The
duration
is the length of the animation. - The
debugLabel
is a string used to identify the animation during debugging (used by toString). - The
lowerBound
parameter is the smallest value this animation can obtain and the value at which this animation is deemed to be dismissed. It can’t be null. - The
upperBound
parameter is the largest value this animation can obtain and the value at which this animation is deemed to be completed. It can’t be null. - The
vsync
parameter is theTickerProvider
for the current context. It is required and can’t be null. See TickerProvider for information on obtaining a ticker provider.
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
An AnimationController
needs a TickerProvider
to send the signal to mark the frames of the animation from beginning to end. The TickerProvider
is configured using the vsync
argument on the constructor. If you are creating an AnimationController
from a State, then you can use the TickerProviderStateMixin
and SingleTickerProviderStateMixin
classes.
The AnimationController
is a stateful Animation<double>
that uses a Ticker to mark the animation frame changes. For each tick, the controller takes the time elapsed since the animation started and passes it to a Simulation
to obtain a value. The value is used to report the animation status. When the Simulation
reports a value indicating the end of the animation, then the controller stops the animation.
The AnimationController
can be given a lower and upper bound (for example, 0.0-1.0) to animate between, and a duration (for example, milliseconds: 2000).
In a simple case using forward(), reverse() , play() , or resume(), the AnimationController
does a linear interpolation from the lower bound to the upper bound (or vice versa, for the reverse direction) over the specified duration.
- When using
repeat()
, theAnimationController
uses a linear interpolation between the given bounds over the given duration, but does not stop. - When using
animateTo()
, theAnimationController
does a linear interpolation over the given duration from the current value to the given target. If no duration is given to the method, the default duration of the controller and the range described by the controller’s lower bound and upper bound is used to determine the velocity of the animation. - When using
fling()
, aForce
object is used to create a specific simulation which is then used to drive the controller. - When using
animateWith()
, the given simulation is used to drive the controller.
These methods all return the future that the Ticker provides and which will resolve when the controller next stops or changes simulation.
Customized values
In some cases, a position might exceed the 0.0-1.0 value range of the AnimationController
. For example, the fling()
function allows you to provide velocity, force, and position using the Force
object. The position can be anything and so it can be outside of the 0.0-1.0 value range.
For example, a CurvedAnimation
can also exceed the 0.0-1.0 range even if the value range of the AnimationController
doesn’t go out of the default range. Depending on the curve selected, the output of the CurvedAnimation
can have a wider range than the input. Elastic curves such as Curves.elasticIn
will significantly overshoot or undershoot the default range.
AnimationStatus
The status is defined by an enum with four states:
-
dismissed: The animation stopped and the current frame is the first frame.
-
forward: The animation plays from the starting value to the ending value.
-
reverse: The reverse of the forward animation.
-
completed: The animation stops at the last frame.
AnimatedBuilder
AnimatedBuilder
is used for explicit widgets where an animation is included in a larger build function. AnimatedBuilder creates a widget that can be animated by the controller. Use AnimatedBuilder
widget to describe an animation as part of a build method for another widget.
AnimatedContainer
The AnimatedContainer
is a container that gradually changes its values over a period of time. The AnimatedContainer
automatically animates between the old and new values of properties when they change using the specified curve
and duration
. Properties that are null are not animated.
The AnimatedContainer
widget is an example of a simple implicit animation between different parameters. It has an internal AnimationController
that has already been defined in the widgets library.
AnimatedWidget
The AnimatedWidget
class allows you to separate the widget code from the animation code in the setState()
call. AnimatedWidget
doesn’t need to maintain a State object to hold the animation.
Use AnimatedWidget
when you want to create or reuse a transition animation such as FadeTransition
or SizeTransition
.
For more information, see Flutter transition animations
Curves
A CurvedAnimation
defines the animation progress as a non-linear curve.
final CurvedAnimation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeIn);
The Curves class defines many commonly used curves, or you can create your own. For example:
class ShakeCurve extends Curve {
@override
double transform(double t) {
return math.sin(t * math.PI * 2);
}
}
CurvedAnimation
and the AnimationController
are both of type Animation<double>
, so you can pass them interchangeably. The CurvedAnimation
wraps the object it’s modifying—you don’t subclass AnimationController
to implement a curve.
Listeners and notifications
Listeners
and StatusListeners
are used to monitor animation state changes.
An Animation
object can have Listeners
and StatusListeners
defined with addListener()
and addStatusListener()
. A Listener
is called whenever the value of the animation changes. The most common behavior of a Listener
is to call setState()
to cause a rebuild. A StatusListener
is called when an animation begins, ends, moves forward, or moves reverse, as defined by AnimationStatus
.
See the Rendering animation tutorial for an example showing how to use the addListener()
method.
See Monitoring the progress of the animation tutorial for an example showing how to use addStatusListener()
method.
When the animation’s value changes and the animation notifies all the listeners added with addListener
. Typically, a State
object that listens to an animation will call setState
on itself in its listener callback to notify the widget system that it needs to rebuild with the new value of the animation.
Physics-based animation
In physics-based animation, motion is modeled to resemble real-world behavior. When you toss a ball, for example, where and when it lands depends on how fast it was tossed, how heavy it is, and how far off the ground. Similarly, dropping a ball attached to a spring falls and bounces differently than dropping a ball attached to a string. The Flutter gallery provides a demo app for Material design widgets that includes and example. Under Material Components, the Grid example demonstrates a fling animation. Select one of the images from the grid and zoom in. You can pan the image with flinging or dragging gestures. For more information, see the API documentation for AnimationController.animateWith and SpringSimulation.
Scheduler
(NEED better description of the scheduler)
The SchedulerBinding
is a singleton class that exposes the Flutter scheduling primitives.
For this discussion, the key primitive is the frame callbacks. Each time a frame needs to be shown on the screen, Flutter’s engine triggers a “begin frame” callback which the scheduler multiplexes to all the listeners registered using scheduleFrameCallback()
. These callbacks are given the official time stamp of the frame, in the form of a Duration
from some arbitrary epoch. Since all the callbacks have the same time, any animations triggered from these callbacks will appear to be exactly synchronized even if they take a few milliseconds to be executed.
Simulations
The Simulation
abstract class maps a relative time value (an elapsed time) to a double value, and has a notion of completion.
In principle, simulations are stateless but in practice, some simulations (for example, BouncingScrollSimulation
and ClampingScrollSimulation
) change state irreversibly when queried.
There are various concrete implementations of the Simulation
class for different effects.
Tickers
The Ticker
class hooks into the scheduler’s scheduleFrameCallback()
mechanism to invoke a callback for every tick.
A Ticker
can be started and stopped. When started, it returns a Future
that resolves when it is stopped. For each tick, the Ticker
provides the callback with the duration since the first tick after it was started. Because tickers always give their elapsed time relative to the first tick after they were started, tickers are all synchronized. If you start three ticks at different times between two frames, they will all be synchronized with the same starting time, and will subsequently tick in lockstep.
SingleTickerProviderStateMixin
–Provides a single Ticker
that is configured to only tick while the current tree is enabled, as defined by TickerMode
. To create the AnimationController
in a State
that only uses a single AnimationController
, mix in this class, then pass vsync: this
to the animation controller constructor. This mixin only supports vending a single ticker.
TickerProviderStateMixin
—Provides Ticker
objects that are configured to only tick while the current tree is enabled, as defined by TickerMode
. Use this class if you might have multiple AnimationController
objects over the lifetime of the State
. To create an AnimationController
in a class that uses this mixin, pass vsync: this
to the animation controller constructor whenever you create a new animation controller.
The widget test framework WidgetTester
object can be used as a ticker provider for testing purposes. In other contexts, you will need to either pass a TickerProvider
from a higher level for example, indirectly from a State
that mixes in TickerProviderStateMixin
, or create a custom TickerProvider
subclass.
Tweens
Tweening or inbetweening, is the process of generating intermediate frames between two images, called key frames, to give the appearance that the first image transforms smoothly from one key frame to the next key frame. Inbetweens are the drawings which create the illusion of motion. The sequence of frames is called a tween For example, a tween might define an interpolation from blue to yellow, or from 0 to 255.
In a tween animation, the begin and end points are defined, as well as a timeline (duration), and a curve that defines the timing and speed of the transition. The Flutter framework calculates how to transition from the beginning point to the end point. Flutter’s animation widgets enables you to specify objects in an image and define how they should move and change during the tweening process. While some widgets can be used to manually render or customize frames (explicit animations), Flutter also includes widgets that you can use to automatically render the tweens (implicit and transition widgets).
A Tween
is a stateless and immutable object that takes a begin value and an end value of an output type <T>
and a way to interpolate (leap) between the begin and end values. Tween
classes are stateless and immutable. The sole job of a Tween
is to define a mapping from an input range to an output range. The input range is commonly 0.0 to 1.0, but that’s not a requirement. A Tween inherits from Animatable<T>
, not from Animation<T>
. An Animatable, like Animation, isn’t required to output a double. For example, ColorTween
specifies a progression between two colors and a CurveTween
transforms the value of the given animation by a given curve.
By itself, a tween defines how to interpolate between two values. To get a concrete value for the current frame of an animation, you also need an animation to determine the current state. There are two ways to combine a tween with an animation to get a concrete value:
- You can evaluate the tween at the current value of an animation. This approach is useful for widgets that are already listening to the animation and rebuilding whenever the animation changes value.
- You can animate the tween based on the animation. Rather than returning a single value, the animate function returns a new
Animation
that incorporates the tween. This approach is useful when you want to give the newly created animation to another widget, which can then read the current value that incorporates the tween as well as listen for changes to the value.
A Tween object does not store any state. Instead, it provides the evaluate (Animation<double> animation
) method that applies the mapping function to the current value of the animation. The current value of the Animation
object can be found in the .value
method. The evaluate function also ensures that the begin and end values are returned when the animation values are 0.0 and 1.0, respectively.
Customizing the interpolation
By default, the AnimationController
object ranges from 0.0 to 1.0. If you need a different range or a different data type, you can use a Tween
to configure an animation to interpolate to a different range or data type.
final Tween doubleTween = new Tween<double>(begin: -200.0, end: 0.0);
Many types have specific Tween
subclasses that provide type-specific interpolation. For example, ColorTween
interpolates between colors and RectTween
interpolates between rectangles.
final Tween colorTween =
new ColorTween(begin: Colors.transparent, end: Colors.black54);
You can define your own interpolations by creating your own subclass of Tween
and overriding its linear interpolation (lerp) function.
Tween examples
To use a Tween object, call animate()
on the Tween
(Tween.animate
), passing in the controller object. For example, the following code generates the integer values from 0 to 255 over the course of 500 ms. Notice that animate()
returns an Animation
, not an Animatable
.
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(controller);
The following example shows a controller, a curve, and a Tween:
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(curve);
vsync
The vsync
parameter prevents offscreen animations from consuming unnecessary resources. You can use your stateful object as the vsync
by adding SingleTickerProviderStateMixin
to the class definition or, if you have multiple AnimationController
objects, you can use the TickerProviderStateMixin
.
An AnimationController
requires a TickerProvider
which is configured using the vsync
argument on the constructor.
The vsync
parameter can be changed by calling resync
.