The paper
In the previous two blogs, we looked at how animation can be easily used in Flutter and learned about How Tween can use it. However, in many scenes, we do not use a single animation, but multiple animations executed together or in sequence, so what should we do in dealing with such scenes? Today, we are going to talk about how to implement parallel or serial animation in Flutter. Let’s take a look at how these two forms of animation are implemented.
Parallel animation
For parallel Animation, where multiple animations are executed at the same time, we need to keep the AnimationController of each Animation consistent. This has been illustrated by Tween in the last article. I’m just going to take the code.
Create the animation first and keep the animation controller consistent. An example is shown in the following code.
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
_animation = Tween<double>(begin: 0, end: 50).animate(_animationController) .. addListener(() { setState(() {}); }); _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_animationController) .. addListener(() { setState(() {}); });Copy the code
Then use the values of _animation and _colorAnimation directly in the build method, as shown below.
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
Copy the code
Serial animation
Compared with parallel animation, serial animation is more complicated to write. There are three implementation schemes of serial animation, namely, listening state method, Interval time Interval method and TweenSequence animation sequence method. Let’s take a look at the three implementations and the differences.
Monitoring state method
The state monitoring method uses the AnimationController to listen for the completed state of the animation, and then to execute the next animation, and so on until all animations are completed.
For example, now we need to implement the component offset by 50 units in 0.3 seconds, and then perform the component color change from orange to red in 0.6 seconds.
First, we declare the shift animation controller and color animation controller as well as the shift animation and color animation, as shown below.
AnimationController _animationController;
Animation<double> _animation;
AnimationController _colorAnimationController;
Animation<Color> _colorAnimation;
Copy the code
Next, we create an animation controller for displacement, color, and animation, as shown below.
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
_animation = Tween<double>(begin: 0, end: 50).animate(_animationController) .. addListener(() { setState(() {}); }); _colorAnimationController = AnimationController(duration:Duration(milliseconds: 600), vsync: this); _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_colorAnimationController) .. addListener(() { setState(() {}); });Copy the code
Finally, we just need to monitor the state of the displacement animation to execute the color animation, as shown below.
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_colorAnimationController.forward();
};
});
Copy the code
The overall Demo code is shown below.
class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
AnimationController _colorAnimationController;
Animation<Color> _colorAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
_animation = Tween<double>(begin: 0, end: 50).animate(_animationController) .. addListener(() { setState(() {}); }); _colorAnimationController = AnimationController(duration:Duration(milliseconds: 600), vsync: this); _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_colorAnimationController) .. addListener(() { setState(() {}); }); _animationController.addStatusListener((status) {if (status == AnimationStatus.completed) {
_colorAnimationController.forward();
};
});
}
void startEasyAnimation() {
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"Click to execute simplest animation", style: TextStyle(color: Colors.black38), ), ), ], ), ), ); }}Copy the code
Interval Indicates the Interval method
The state listening above requires one animation to write one Controller, and basically requires each Controller to listen for completion before starting the next Controller. If there are dozens of animations in a process, think of it as brain buzzing. So next, we will introduce the second solution – Interval time Interval method.
The whole idea of Interval is that an animation Controller controls the execution of all animations. Then each animation only needs to confirm its time weight in the entire animation.
First, declare an animation Controller and multiple animations.
AnimationController _animationController;
Animation<double> _animation;
Animation<Color> _colorAnimation;
Copy the code
Initialization and then AnimationController AnimationController animation time (duration) is set to the total length of all the animation, for example, here I set to 600 milliseconds (_animation length: 300 milliseconds, _colorAnimation Duration :300 ms).
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
Copy the code
The next step is to initialize the two Animation objects. Instead of passing in the AnimationController directly, the Tween object calls animate(), passing in a CurvedAnimation object. Two parameters are passed to CurvedAnimation. One is parent, which specifies the AnimationController. The other is curve, which specifies the animation curve function. We can use the usual animation curve functions, or we can generate them ourselves, which we did here. Specifies the time interval for animation execution.
// Create CurvedAnimation
CurvedAnimation({
required this.parent,
required this.curve,
this.reverseCurve,
}) : assert(parent ! =null),
assert(curve ! =null) {
_updateCurveDirection(parent.status);
parent.addStatusListener(_updateCurveDirection);
}
Copy the code
Since the time lengths of the two animations are divided, each of which is 300 milliseconds, the value of curve parameter is Interval(0.0, 0.5) and Interval(0.5, 1.0) respectively. The initialization process of the two animations is shown as follows.
_animation = Tween<double>(begin: 0, end: 50).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.0.0.5),),).. addListener(() { setState(() {}); }); _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate( CurvedAnimation( parent: _animationController, curve: Interval(0.5.1.0),),).. addListener(() { setState(() {}); });Copy the code
The overall Demo code is shown below.
class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
Animation<Color> _colorAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
_animation = Tween<double>(begin: 0, end: 50).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.0.0.5),),).. addListener(() { setState(() {}); }); _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate( CurvedAnimation( parent: _animationController, curve: Interval(0.5.1.0),),).. addListener(() { setState(() {}); }); }void startEasyAnimation() {
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"Click to execute simplest animation", style: TextStyle(color: Colors.black38), ), ), ], ), ), ); }}Copy the code
TweenSequence method
Although the above two solutions can solve the problems of animation group, but they are too complicated, then is there a more elegant solution? This requires the use of the TweenSequence and TweenSequenceItem classes. TweenSequence is the animation group class, and TweenSequenceItem is the class that defines the concrete implementation of each animation. However, TweenSequence and TweenSequenceItem are not perfect. The biggest problem with TweenSequence and TweenSequenceItem is that the attribute value types must be the same.
Again, let’s change the Margin example by moving the view component up 50 and showing the view how to use the TweenSequence and TweenSequenceItem to animate it.
First, we declare an Animation controller, AnimationController, and Animation.
AnimationController _animationController;
Animation<double> _animation;
Copy the code
Still, the total animation time for both is 600 milliseconds, so we need to implement the AnimationController as shown below.
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
Copy the code
Then, we implement the Animation through the TweenSequence and TweenSequenceItem classes.
Implement two TweenSequenceItems. The weight property of the TweenSequenceItem is used to set the time weight of the animation execution. This is the ratio of the current animation execution time to the total animation time in the entire animation process. For example, the ratio of interpolation time for the first animation below is 50/(50 + 100). The second animation interpolation takes 100/(50 + 100).
TweenSequenceItem downMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 1.0, end: 50.0),
weight: 50,); TweenSequenceItem upMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 50.0, end: 100.0),
weight: 100,);Copy the code
Then create an animation interpolation group and put the above two animation interpolations into the group.
TweenSequence tweenSequence = TweenSequence<double>([
downMarginItem,
upMarginItem,
]);
Copy the code
Finally, it’s OK to generate the animation.
_animation = tweenSequence.animate(_animationController);
_animation.addListener(() {
setState(() {});
});
Copy the code
Of course, the above three steps can be condensed into one piece of code, but I’ve just broken it down to illustrate each one.
// Abbreviate code
_animation = TweenSequence<double>([
TweenSequenceItem<double>(
tween: Tween(begin: 0.0, end: 50.0),
weight: 50,
),
TweenSequenceItem<double>(
tween: Tween(begin: 50.0, end: 100.0),
weight: 100, ), ]).animate(_animationController) .. addListener(() { setState(() {}); });Copy the code
The overall code is shown below.
class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
@override
void initState(a) {
super.initState();
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
TweenSequenceItem downMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 1.0, end: 50.0),
weight: 50,); TweenSequenceItem upMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 50.0, end: 100.0),
weight: 100,); TweenSequence tweenSequence = TweenSequence<double>([
downMarginItem,
upMarginItem,
]);
_animation = tweenSequence.animate(_animationController);
_animation.addListener(() {
setState(() {});
});
}
void startEasyAnimation(a) {
_animationController.forward();
}
@override
void dispose(a) {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: Colors.orangeAccent,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"Click to execute simplest animation", style: TextStyle(color: Colors.black38), ), ), ], ), ), ); }}Copy the code
Animation group implementation summary
Now that we’ve covered the above three implementations, let’s compare the differences.
features | Monitoring state method | Interval Indicates the Interval method | TweenSequence method |
---|---|---|---|
Code simplicity | 🔅 🔅 | 🔅 🔅 🔅 | 🔅 🔅 🔅 🔅 🔅 |
Whether the animation is interlactable | ❌ | ✅ | ❌ |
Whether animation properties can be changeable | ✅ | ✅ | ❌ |
Interlactable animation: Interlactable animation mainly refers to whether the next animation can be executed only after the previous animation has been completely executed.
Animation properties can be changeable: Animation properties can be changeable refers to the current animation process can have more than one variable properties, such as changing size and color, etc.
conclusion
OK, how to use Flutter to realize serial animation and parallel animation is described here, the next article will talk about the common fly-in and fly-out animation in transition animation – Hero animation, welcome to continue to pay attention to SAO Dong, if you have any questions, please contact SAO Dong.