This article is translated from 👉 official Flutter tutorial. By the end of this article you will know when to use AnimatedWidget and when to use AnimatedBuilder using the key classes introduced in previous articles.
The following is the text of the article
This tutorial shows developers how to develop explicit animations in Flutter. Write introductions to some key concepts, classes, and methods in the animation repository, and develop 5 animation examples using these key points. These animations build interspersed with each other, a more comprehensive display of animation library.
The Flutter SDK also has built-in display animations, such as FadeTransition, SizeTransition, and SlideTransition. These simple animations only need to set the start and end points to trigger, which is much easier than custom display animations. The custom of
This tutorial shows you how to build explicit animations in Flutter. After introducing some of the basic concepts, classes, and methods in the animation library, it walks you through five animation examples. These examples build on each other to introduce you to different aspects of the animation library. This article will cover all of these animations.
Key concepts and key classes
Animation
The core class of the Flutter animation library, which provides values for animationsAnimation
The object knows the state of the animation, but not the UI that the animation displays on the screenAnimationController
managementAnimation
CurvedAnimation
Define a non-linear animation processTween
Provides tween or effect values for the animation, such as tween can be defined from red to blue and so on- use
Listener
å’ŒStatusListener
To listen for changes in animation state
Widgets can use animation in one of two ways: either by directly using the animation values and state values in the build method, or by passing the animation to other widgets, which use the passed animation for more complex effects.
Animation<double>
In Flutter, the Animation object does not know what is displayed on the screen. It is just an abstract class that defines the current Animation value and Animation state. The most common type of Animation is Animation
.
The interpolation generated by the Animation object may be linear, curving, jumping, or a mapping defined by the developer. As long as we control the Animation object, we can reverse it and even switch the direction of the Animation in the middle.
The Animation can also be of a specified type, such as Color Animation
The Animation object is stateful. The.value member variable retrieves the current value of the Animation.
The Animation has no awareness of the render and build() methods.
CurvedAnimation
CurvedAnimation defines the process of using nonlinear curves to define the animation.
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
Copy the code
Note that Curves define many common animation Curves, and developers can customize them as follows:
import 'dart:math'; class ShakeCurve extends Curve { @override double transform(double t) => sin(t * pi * 2); } Copy the code
You can browse the effects of the Curves built into Flutter in the Curves documentation.
Both CurvedAnimation and AnimationController are subclasses of Animation< Double >, so they can be passed to each other. So what’s the difference? CurvedAnimation modifies the original animation value to achieve the curve effect. But AnimationController does not need generalization to implement curves.
AnimationController
By default, an AnimationController eal the numbers from 0.0 to 1.0 during a given duration. For example, this code creates an Animation object, but does not start it running:
The AnimationController is a special Animation object that associates the creation of an Animation value with a frame. Whenever a new frame is created, an Animation value is generated. By default, the AnimationController generates values linearly between 0.0 and 1.0 over the animation’s time range. For example, the following code constructs an Animation object:
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
Copy the code
In terms of class structure, AnimationController is a generalization of Animation< Double >, so you can pass an AnimationController object wherever an Animation object is needed. In contrast to Animation
, AnimationController provides methods for Animation control, such as calling.forward() to start an Animation.
As mentioned above, the AnimationController associates value generation with screen refresh, so it typically generates 60 numbers per second. Whenever a new value is generated, the Listener is called. If you want to create a custom display list at frame generation, look at the RepaintBoundary. This component defines the boundaries for drawing.
When creating the AnimationController object, you need to pass a vsync parameter. Vsync is used to reduce animation consumption when the screen goes out. In the State of StatefulWidget use animation, can be mixed with SingleTickerProviderStateMixin. You can refer to 👉animate1.
Tween
In general, AnimationController varies from 0.0 to 1.0. If you want different ranges, different data types, then you can use Tween, and Tween defines the interpolation range or the interpolation type for the animation. For example, the following defines -200.0 to 0.0:
tween = Tween<double>(begin: - 200., end: 0);
Copy the code
Tween is stateless, accepts only begin and end, and does the mapping from input to output. The input is generally 0.0 to 1.0, but is not fixed.
Tween inherits from Animatable
, not Animation
. Animatable does not need to output a value of type double. For example, ColorTween specifies how two colors change.
colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);
Copy the code
The Tween object does not store any state. It defines the evaluate(Animation
Animation) method, which applies the value of the current Animation to the mapping function. The.value of the Animation takes the value of the current Animation. The Evaluate method also performs some general operations, such as returning begin at 0.0 and end at 1.0.
Tween.animate
To use the Tween procedure, you call Tween’s animate() method, which requires a Controller object. For example: generate a series of int values from 0 to 255 in 500 ms.
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);
Copy the code
Note that the animate() method returns an Animation object, not an Animatable object.
The following code combines: Controller, curve, tween:
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation<double> curve =
CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);
Copy the code
Animation to inform
You can call addListener() and addStatusListener() to listen for value changes and state changes. SetState () is usually called in addListener() to rebuild. AddStatusListener () is usually concerned with state changes such as the beginning, end, movement, and reversal of the animation.
Animation example
Here are five examples of animations
Rendering the animation
- How to use
addListener()
 和ÂsetState()
To animate a normal Widget- Every time you generate an animation value, it’s in
addListener()
Method callsetState()
- How do YOU define a band
vsync
Must parameterAnimationController
- Through the”
..addListener
“Understand”.
“Key words,- use
_
Start by making the class private
So far we know how to generate the values required by the animation, but we have not yet bound the values to the display. To render using an Animation object, we use the Animation object as a member variable of our Widget and then decide how to draw using the value of the Animation.
Here is the Logo of the Flutter without drawing effect
import 'package:flutter/material.dart';
void main() => runApp(const LogoApp());
class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: 300,
width: 300,
child: constFlutterLogo(), ), ); }}Copy the code
Logo animation from nothing to full screen requires the following steps:
Step 1: Define the AnimationController, passing in the animation duration and vsync. The structure of the vsync needed a SingleTickerProviderStateMixin
Step 2: Use Tween
to convert 0-1 to 0-300
Step 3: Add a listener for the animation, call setState in the body of the addListener method, and continue the build
Step 4: Change the specified width to the width of the animation
Step 5: Dispose to release controller to avoid memory leak
The code is as follows:
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
/ / the first step
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
// #docregion addListener
/ / the second step
animation = Tween<double>(begin: 0, end: 300).animate(controller) .. addListener(() {// #enddocregion addListener
/ / the third step
setState(() {
// The state that has changed here is the animation object’s value.
});
// #docregion addListener
});
// #enddocregion addListener
controller.forward();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
/ / step 4
margin: const EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
@override
void dispose() {
/ / step 5
controller.dispose();
super.dispose(); }}Copy the code
The effect is as follows:
Where the code declares the animation, it uses.. Represents the return value based on the call method.
animation = Tween<double>(begin: 0, end: 300).animate(controller) .. addListener(() {/ /...
});
animation = Tween<double>(begin: 0, end: 300).animate(controller);
animation.addListener(() {
/ /...}); These two pieces of code have the same meaning, declaring the animation object and adding listenersCopy the code
Simplify using AnimatedWidget
- How to use
AnimatedWidget
Instead ofaddListener
 +ÂsetState
To create an animation- use
AnimatedWidget
Create a Widget that can reuse animations.AnimatedBuilder
Widgets can be separated from animations.
The AnimatedWidget
 base class allows you to separate out the core widget code from the animation code. AnimatedWidget
 doesn’t need to maintain a State
 object to hold the animation. Add the following AnimatedLogo
 class:
The AnimatedWidget base class separates the core Widget from the animation code. The AnimatedWidget no longer needs to maintain the State object to which the animation was added. As follows:
import 'package:flutter/material.dart';
void main() => runApp(const LogoApp());
// #docregion AnimatedLogo
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: constFlutterLogo(), ), ); }}// #enddocregion AnimatedLogo
class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller);
controller.forward();
}
@override
Widget build(BuildContext context) => AnimatedLogo(animation: animation);
@override
void dispose() {
controller.dispose();
super.dispose(); }}Copy the code
In contrast to the first code, AnimatedLogo is dedicated to creating the Widget that performs the animation, and LogoApp is where the animation object is generated. So the two are separated. The effect is the same.
Listen for animation
In general, developers often need to know about changes in animation state, such as completed, forward, reversed, and so on. We can be notified by addStatusListener(). We can modify the previous example to listen for and print the state values of the animation.
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller)
/ / first place
..addStatusListener((state) => print('$state'));
controller.forward();
}
// ...
}
Copy the code
Looking at the first code, we added a state listener and printed the state. The console will print:
AnimationStatus.forward
AnimationStatus.completed
Copy the code
We can use state monitoring to achieve an infinite loop. At 1, reverse animation. It’s animating forward at zero.
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller) .. addStatusListener((status) {/ / first place
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
})
controller.forward();
}
// ...
}
Copy the code
Refactor using AnimatedBuilder
We had a bit of a problem with our previous LogoApp and AnimatedLogo code, we would call the _LogoAppState build method every time we animated, and the new AnimatedLogo would be built, and the AnimatedLogo would be built, Each time a new FlutterLogo is built, it doesn’t need to be built, just build the Container on top of it and change the size. So the problem is building FlutterLogo multiple times. One solution to this problem is to separate functionality into different classes:
- Rendering logo
- define
Animation
object - Rendering the animation
You can use AnimatedBuilder to achieve the separation effect. AnimatedBuilder is a separate class in the render tree. Like the AnimatedWidget, the AnimatedBuilder automatically listens for notifications from the Animation object, such as value changes, state changes, and automatically marks the Widget tree as dirty when the Animation value changes. So developers don’t need to call the addListener() method themselves.
The code is as follows:
import 'package:flutter/material.dart';
void main() => runApp(const LogoApp());
// #docregion LogoWidget
class LogoWidget extends StatelessWidget {
const LogoWidget({Key? key}) : super(key: key);
// Leave out the height and width so it fills the animating parent
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10),
child: constFlutterLogo(), ); }}// #enddocregion LogoWidget
// #docregion GrowTransition
class GrowTransition extends StatelessWidget {
const GrowTransition({required this.child, required this.animation, Key? key})
: super(key: key);
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
returnSizedBox( height: animation.value, width: animation.value, child: child, ); }, child: child, ), ); }}// #enddocregion GrowTransition
class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
// #docregion print-state
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller);
controller.forward();
}
// #enddocregion print-state
@override
Widget build(BuildContext context) {
return GrowTransition(
child: const LogoWidget(),
animation: animation,
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
// #docregion print-state
}
Copy the code
The Widget tree formed by the code above looks like this:
Starting at the bottom of the Widget tree, a LogoWidget is a component that renders a logo. It’s simple:
class LogoWidget extends StatelessWidget {
const LogoWidget({Key? key}) : super(key: key);
// Leave out the height and width so it fills the animating parent
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10),
child: const FlutterLogo(),
);
}
}
Copy the code
LogoWidget removes the width and height Settings and fills the parent layout. It doesn’t care what the parent layout is.
The middle three blocks are all built in the GrowTransition build() method, as shown in the code below. The GrowTransition itself is stateless and holds the final animation property that defines the animation. The build() method constructs the AnimatedBuilder, which takes anonymous methods and LogoWidget objects as input parameters. Rendering and animation are in the anonymous method. We see that the width and height of the anonymous method’s SizedBox is the value of the animation, which forces the child (LogoWidget) to be the width and height of the animation to achieve the effect of animation scaling.
class GrowTransition extends StatelessWidget {
const GrowTransition({required this.child, required this.animation, Key? key})
: super(key: key);
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
returnSizedBox( height: animation.value, width: animation.value, child: child, ); }, child: child, ), ); }}Copy the code
The code that puts this together is kind of like the original animation code. Create the AnimationController and Tween in initState() and associate them using animate(). Isn’t it amazing how this animation works? The build() method returns GrowTransition, which takes a byte point, LogoWidget, and an animation object, which is listed above.
At the same time, the animation
Below, we mentioned in the previous use of animation state to achieve the cycle on the basis of the realization of simultaneous animation effect. AnimatedWidget implements the animation process from scratch. Imagine what we did from scratch, and then we added a change in transparency.
The example mainly shows using multiple Tween’s on a controller, with each tween implementing one animation effect.
controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); sizeAnimation = Tween<double>(begin: 0, end: 300).animate(controller); OpacityAnimation = Tween<double>(begin: 0.1, end: 1). Animate (controller);Copy the code
Sizeanimation. value gets the value of the sizeAnimation, and opacityanimation. value gets the value of the transparency animation. But the AnimatedWidget only needs an Animation object. To solve this problem, the method shown in the example is AnimatedLogo to create its own Tween object and display the computed value.
The AnimatedLogo encapsulates its Tween object and violates the law by calling Tween. Evaluate () in the build method, which evaluates the value of the parent animation. The result of the calculation is the size and transparency values.
The code looks like this:
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
// Make the Tweens static because they don't change.
static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
static final _sizeTween = Tween<double>(begin: 0, end: 300);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
child: constFlutterLogo(), ), ), ); }}class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this); animation = CurvedAnimation(parent: controller, curve: Curves.easeIn) .. addStatusListener((status) {if (status == AnimationStatus.completed) {
controller.reverse();
} else if(status == AnimationStatus.dismissed) { controller.forward(); }}); controller.forward(); }@override
Widget build(BuildContext context) => AnimatedLogo(animation: animation);
@override
void dispose() {
controller.dispose();
super.dispose(); }}Copy the code
The previous code remains the same, just the AnimatedLogo. The overall effect is as follows:
conclusion
This section mainly covers the basics of Flutter animation, such as Tweens, AnimaitonController, etc. There are many more classes for developers to explore. If you want to see complex Tweens, Material designed animations, Hero animations, etc., you can check out the previous article which allows you to talk about a couple of sentences on Flutter animations. There are many resources.