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.