[actual combat] animation core

Time:2021-9-11

[actual combat] animation core

Lao Meng: animation system is the core function of any UI framework. It is also the top priority for developers to learn a UI framework. At the same time, it is also a difficult part to master. Next, we will unveil the veil of fluent animation layer by layer.

The animation principle of any program is the same, that is, visual persistence, which is also called visual pause. When the human eye observes the scene, the light signal is transmitted to the brain nerve, which takes a short time. After the action of light, the visual image does not disappear immediately. This residual vision is called “afterimage”, and this phenomenon of vision is called “visual persistence”.

The human eye can retain images for about 0.1-0.4 seconds, so if you see 25 consecutive images in 1 second, you will feel the picture is smooth, and how many consecutive images you see in 1 second is called the frame rate, i.eFPSIn theory, it can reach 24 fps, and the picture is relatively smooth, while the fluent can reach 60 FPs in theory.

AnimationController

The basic principle of the animation system is introduced, and the animation effect of changing the size of a blue box from 100 to 200 is realized:

class AnimationBaseDemo extends StatefulWidget {
  @override
  _AnimationBaseDemoState createState() => _AnimationBaseDemoState();
}

class _AnimationBaseDemoState extends State<AnimationBaseDemo> {
  double _size = 100;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
          setState(() {
            _size = 200;
          });
        },
        child: Container(
          height: _size,
          width: _size,
          color: Colors.blue,
          alignment: Alignment.center,
          Child: text ('point I get bigger ', style: TextStyle (color: colors. White, fontsize: 18),),
        ),
      ),
    );
  }
}

[actual combat] animation core

Although it becomes larger, it has no animation effect, but directly becomes larger. If you want to enlarge it a little, you need to introduce itAnimationController, it is an animation controller, which controls the start and stop of animation, and can also obtain the running state of animation. The animationcontroller is usually initialized in the initstate method:

class _AnimationBaseDemoState extends State<AnimationBaseDemo> with SingleTickerProviderStateMixin{
  double _size = 100;
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500));
  }
  ...
}

There are two parameters to be set:

  • vsync: when creating animationcontroller, you need to pass avsyncParameter, existsvsyncIt will prevent off screen animation from consuming unnecessary resources. It can be used when a single animation controller is usedSingleTickerProviderStateMixin, multiple animationcontrollers are usedTickerProviderStateMixin
  • duration: indicates when the animation is executed.

Amend as follows:

class _AnimationBaseDemoState extends State<AnimationBaseDemo> with SingleTickerProviderStateMixin{
  double _size = 100;
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500));
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
          _controller.forward();
        },
        child: Container(
          height: _size,
          width: _size,
          color: Colors.blue,
          alignment: Alignment.center,
          Child: text ('point I get bigger ', style: TextStyle (color: colors. White, fontsize: 18),),
        ),
      ),
    );
  }
  
  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }
}

When you click the blue box, you no longer change the size directly, but execute the animation_controller.forward()

In addition, in statedisposeRelease animationcontroller in the lifecycle.

At this time, click the blue box to find that it will not become larger. The statefulwidget component needs to be called to change its appearancesetStateTherefore, add listening to animationcontroller:

_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500))
..addListener(() {
  setState(() {
    _size = 100+100*_controller.value;
  });
});

Every frame will be recalledaddListener, set the size of the blue box in this callback. The size of the blue box changes from 100 to 200, and the value of animationcontroller is 0 to 1 by default, so the size of the blue box is equal to_ size = 100+100*_ Controller.value, running effect:

[actual combat] animation core

This is the simplest way to realize animation in fluent, the most important of which isAnimationController,_ Controller.value is the value of the current animation, from 0 to 1 by default. You can also set the maximum and minimum values in the form of parameters:

_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500),lowerBound: 100,upperBound: 200)
..addListener(() {
  setState(() {
    _size = _controller.value;
  });
})

At this time_ The value of controller.value changes from 100 to 200.

In addition to using addListener to monitor each frame, you can also monitor the change of animation state:

_controller = AnimationController(
    vsync: this,
    duration: Duration(milliseconds: 500),
    lowerBound: 100,
    upperBound: 200)
  ..addStatusListener((status) {
    print('status:$status');
  })

There are four states of animation:

  • dismissed: the animation stops at the beginning.
  • forward: animation is running from start to end (forward).
  • reverse: animation is running from end to start (reverse).
  • completed: the animation stops at the end.

Let’s look at the control method of animation:

  • forward: forward animation.
  • reverse: reverse the animation.
  • repeat: repeat the animation.
  • reset: reset animation.

Both forward and reverse methods have a from parameter. The meaning of this parameter is the same. It means that the animation starts from this value instead of from Lowerbound to upperbound. For example, in the above example, if the from parameter is set to 150, when the animation is executed, the blue box instantly becomes 150, and then slowly becomes 200.

Let the size of the blue box change from 100 to 200, then to 100, then to 200, and so on:

_controller = AnimationController(
    vsync: this,
    duration: Duration(milliseconds: 500),
    lowerBound: 100,
    upperBound: 200)
  ..addStatusListener((AnimationStatus status) {
    if(status == AnimationStatus.completed){
      _controller.reverse();
    }else if(status == AnimationStatus.dismissed){
      _controller.forward();
    }
  })
  ..addListener(() {
    setState(() {
      _size = _controller.value;
    });
  });

Just monitor the change of animation state and execute the animation again in the forward / reverse direction after the animation is completed.

[actual combat] animation core

Although there is a lot to be said above, there is only one key pointAnimationController

AnimationControllerThe minimum / maximum value set is double. What should I do if the change of animation is color?

The values returned by the animationcontroller during animation execution are 0 to 1, and the color changes from blue to red as follows:

_controller =
    AnimationController(vsync: this, duration: Duration(milliseconds: 500))
      ..addListener(() {
        setState(() {
          _color = Color.lerp(_startColor, _endColor, _controller.value);
        });
      });

The point isColor.lerpMethod, which is a linear interpolation between two colors.

The complete code is as follows:

class TweenDemo extends StatefulWidget {
  @override
  _TweenDemoState createState() => _TweenDemoState();
}

class _TweenDemoState extends State<TweenDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
    Color _startColor = Colors.blue;
  Color _endColor = Colors.red;

  Color _color = Colors.blue;

  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 500))
          ..addListener(() {
            setState(() {
              _color = Color.lerp(_startColor, _endColor, _controller.value);
            });
          });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
          _controller.forward();
        },
        child: Container(
          height: 100,
          width: 100,
          color: _color,
          alignment: Alignment.center,
          child: Text(
            'point I change color',
            style: TextStyle(color: Colors.white, fontSize: 18),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }
}

[actual combat] animation core

The behavior of converting from 0 – > 1 to blue – > red in fluent is calledTween (mapping)

Use tween to complete the animation blue – > Red:

class _TweenDemoState extends State<TweenDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<Color> _animation;

  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 500))
          ..addListener(() {
            setState(() {});
          });
    _animation =
        ColorTween(begin: Colors.blue, end: Colors.red).animate(_controller);
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
          _controller.forward();
        },
        child: Container(
          height: 100,
          width: 100,
          color: _animation.value,
          alignment: Alignment.center,
          child: Text(
            'point I change color',
            style: TextStyle(color: Colors.white, fontSize: 18),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }
}

The effect is the same as above.

Tween is only a mapping, and the animation control is still controlled by the animation controller, so it is necessary toTween.animate(_controller)Pass the controller to tween.

The system provides a large number of Tweens:

[actual combat] animation core

Basically, common attributes include their corresponding tweens. Take a look at the source code implementation of colortween:

[actual combat] animation core

In essence, it is also usedColor.lerpImplemented.

Curve

Another important concept in animation isCurve, that is, the animation execution curve. The function of curve is the same as that of interpolator in Android. It is responsible for controlling the change rate of animation. Generally speaking, it is to make the effect of animation change at various rates such as uniform speed, acceleration, deceleration and parabola.

The size of the blue box increases from 100 to 200, and the animation curve is set toBouncein (spring effect)

class CurveDemo extends StatefulWidget {
  @override
  _CurveDemoState createState() => _CurveDemoState();
}

class _CurveDemoState extends State<CurveDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation _animation;

  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 1000))
          ..addListener(() {
            setState(() {});
          });

    _animation = Tween(begin: 100.0, end: 200.0)
        .chain(CurveTween(curve: Curves.bounceIn))
        .animate(_controller);
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
          _controller.forward();
        },
        child: Container(
          height: _animation.value,
          width: _animation.value,
          color: Colors.blue,
          alignment: Alignment.center,
          child: Text(
            'point I get bigger',
            style: TextStyle(color: Colors.white, fontSize: 18),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }
}

[actual combat] animation core

Animation plusCurveAfter, the minimum / maximum value of animationcontroller must be between [0,1]. For example, the following writing is wrong:

_controller =
    AnimationController(vsync: this, duration: Duration(milliseconds: 1000),lowerBound: 100.0,upperBound: 200.0)
      ..addListener(() {
        setState(() {});
      });
_animation = CurveTween(curve: Curves.bounceIn).animate(_controller);

Throw the following exception:

[actual combat] animation core

Correct writing:

_controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 1000))
          ..addListener(() {
            setState(() {});
          });

    _animation = Tween(begin: 100.0, end: 200.0)
        .chain(CurveTween(curve: Curves.bounceIn))
        .animate(_controller);

The system has provided 38 commonly used animation curves:

linear

[actual combat] animation core

decelerate

[actual combat] animation core

bounceIn

[actual combat] animation core

bounceOut

[actual combat] animation core

elasticIn

[actual combat] animation core

Other animation effects can be viewed in official documents.

Usually, these curves can meet 99.99% of the requirements. In many cases, the design tells you that the animation is fast before slow or slow before fast, so choose a similar one, but there are someespeciallyWhat should I do if I have to design an animation curve without a system?

Then customize an animation curve

In fact, the difficulty in customizing an animation curve ismathematicsIn fact, how to realize the mathematical formula in code is the difficulty.

Here is aStair effectAnimation curves for:

[actual combat] animation core

Custom animation curves need to inherit curve and override the transforminternal method:

class _StairsCurve extends Curve {

  @override
  double transformInternal(double t) {
    return t;
  }
}

Returning directly to t is actually linear animation, that isCurves.linear, the animation code to realize the stair effect is as follows:

class _StairsCurve extends Curve {
  //Number of steps
  final int num;
  double _perStairY;
  double _perStairX;

  _StairsCurve(this.num) {
    _perStairY = 1.0 / (num - 1);
    _perStairX = 1.0 / num;
  }

  @override
  double transformInternal(double t) {
    return _perStairY * (t / _perStairX).floor();
  }
}

Modify the case at the beginning and use this curve:

_animation = Tween(begin: 100.0, end: 200.0)
    .chain(CurveTween(curve: _StairsCurve(5)))
    .animate(_controller);

[actual combat] animation core

summary

The core of animation system isAnimationController, and it is indispensable. Animationcontroller must be included in the animation, and tween and curve are supplements to animationcontroller. Tween realizes mapping the value of animationcontroller [0,1] to other types of values, such as color, style, etc. curve is the animation execution curve of animationcontroller, which runs linearly by default.

How to associate animationcontroller, tween and curve:

AnimationController _controller;
Animation _animation;

@override
void initState() {
  super.initState();
  _controller =
      AnimationController(vsync: this, duration: Duration(milliseconds: 1000))
        ..addListener(() {
          setState(() {});
        });

  _animation = Tween(begin: 100.0, end: 200.0)
      .animate(_controller);
}

Or:

_animation = _controller.drive(Tween(begin: 100.0, end: 200.0));

Join curve:

_animation = Tween(begin: 100.0, end: 200.0)
    .chain(CurveTween(curve: Curves.linear))
    .animate(_controller);

Or:

_animation = _controller
    .drive(CurveTween(curve: Curves.linear))
    .drive(Tween(begin: 100.0, end: 200.0));

Only curve is required:

_animation = CurveTween(curve: Curves.linear)
    .animate(_controller);

perhaps

_animation = _controller.drive(CurveTween(curve: Curves.linear));

An animation controller can correspond to multiple animation (Tween or curve). The statefulwidget component can contain multiple animation controllers, but the singletickerproviderstatemixin needs to be modified toTickerProviderStateMixin, change color and size, controlled by two animationcontrollers:

class MultiControllerDemo extends StatefulWidget {
  @override
  _MultiControllerDemoState createState() => _MultiControllerDemoState();
}

class _MultiControllerDemoState extends State<MultiControllerDemo>
    with TickerProviderStateMixin {
  AnimationController _sizeController;
  AnimationController _colorController;
  Animation<double> _sizeAnimation;
  Animation<Color> _colorAnimation;

  @override
  void initState() {
    super.initState();
    _sizeController =
        AnimationController(vsync: this, duration: Duration(milliseconds: 2000))
          ..addListener(() {
            setState(() {});
          });

    _sizeAnimation = _sizeController
        .drive(CurveTween(curve: Curves.linear))
        .drive(Tween(begin: 100.0, end: 200.0));

    _colorController =
        AnimationController(vsync: this, duration: Duration(milliseconds: 1000))
          ..addListener(() {
            setState(() {});
          });

    _colorAnimation = _colorController
        .drive(CurveTween(curve: Curves.bounceIn))
        .drive(ColorTween(begin: Colors.blue, end: Colors.red));
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
          _sizeController.forward();
          _colorController.forward();
        },
        child: Container(
          height: _sizeAnimation.value,
          width: _sizeAnimation.value,
          color: _colorAnimation.value,
          alignment: Alignment.center,
          child: Text(
            'point I change',
            style: TextStyle(color: Colors.white, fontSize: 18),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _sizeController.dispose();
    _colorController.dispose();
  }
}

[actual combat] animation core

Animationcontroller, tween and curve are the basis of the whole animation. The fluent system encapsulates a large number of animation components, but these components are also based on this encapsulation, because it is more important to deeply understand these three parts than to learn to use animation components. Summarize these three parts again:

  • Animation controller: an animation controller that controls the playback and stop of animation. Inherited from animation < double >, it is a special animation object. By default, it will linearly generate a value of 0.0 to 1.0. The type can only be double. Without setting animation curves, you can set the minimum and maximum output values.
  • Curve: animation curve, similar to interpolator in Android, is responsible for controlling the change rate of animation. Generally speaking, it is to make the effect of animation change at various rates such as uniform speed, acceleration, deceleration and parabola.
  • Tween: map the [0,1] value generated by animationcontroller to the values of other attributes, such as color, style, etc.

The process of completing an animation effect is as follows:

[actual combat] animation core

  1. establishAnimationController
  2. if necessaryTweenperhapsCurve, it is not necessary to associate animationcontroller with Tween and curve. Of course, it is necessary in most cases.
  3. The animation value is applied to the component. When there are no tween and curve, the animation value comes from the animationcontroller. If there are tween and curve, the animation value comes from tween or curve animation.

If you find that you can’t write animation after reading this article, don’t lose heart. It’s normal. It’s difficult to understand these abstract concepts for the first time. If you have relevant experience in other platforms, it will be much better. For animation, I think there is only one way to master animationWrite more

Later, we will introduce the basic use, implementation principle, advanced animation and custom animation of animation components. Hand write the usage of each animation component (rather than copy and paste). Looking back at this article, you will have a different feeling.

communication

Laomeng fluent blog address (330 controls usage):http://laomengit.com

Welcome to Flutter exchange group (WeChat: laomengit) and official account [Lao Meng Flutter]:

[actual combat] animation core [actual combat] animation core