As a mobile UI framework, Flutter also has its own animation system.

classification

Flutter animations fall into two categories: Tween and physics-based animations.

This article focuses on the first type of animation.

The basic class of animation

Animation<T>

Animation is an abstract class that stores the state and current value of an Animation. The most commonly used Animation class is Animation<double>

T has many types, such as Color and Offset. More on that later

The value of the current Animation can be obtained by using the value property in the Animation.

Monitor for animation:

  • AddListener () The listener for each frame of animation execution
  • AddStatusListener () listens for changes in animation state. There are four states
AnimationController

The AnimationController inherits Animation<double> and controls the execution, stopping, and so on of the Animation.

The AnimationController will generate a new value every frame of the animation. By default, the AnimationController generates linear numbers from 0.0 to 1.0 (the default range) over a given period of time.

To create the AnimationController, pass in a vsync parameter.

Tween

By default, the AnimationController object ranges from 0.0 to 1.0.

If you need different ranges or data types, you need to tween to configure the animation to generate values for different ranges or data types

The Tween subclass looks like this:

example
class PageState extends State<HomePage> with SingleTickerProviderStateMixin {
  AnimationController controller;
  // Doubler type animation
  Animation<double> doubleAnimation;
  // Color animation
  Animation<Color> colorAnimation;
  // Position animation
  Animation<Offset> offsetAnimation;
  // Rounded corner animation
  Animation<BorderRadius> radiusAnimation;
  // Decorate the animation
  Animation<Decoration> decorationAnimation;
  // Font animation
  Animation<TextStyle> textStyleAnimation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    / / create AnimationController
    controller = new AnimationController(
        vsync: this, duration: Duration(milliseconds: 2000));
    // The first way to create an animation:
    doubleAnimation = new Tween<double>(begin: 0.0, end: 200.0).animate(controller) .. addListener(() { setState(() {}); }).. addStatusListener((AnimationStatus status) {// Execute the command in reverse order
        if (status == AnimationStatus.completed) {
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
        // The reverse execution is complete and the forward execution is completecontroller.forward(); }});// The second way to create an animation is:
    offsetAnimation = controller.drive(
      Tween<Offset>(begin: Offset(0.0.0.0),end: Offset(400.0.200.0))); colorAnimation = ColorTween(begin: Colors.yellow,end: Colors.red).animate(controller); radiusAnimation = BorderRadiusTween(begin: BorderRadius.circular(0),end: BorderRadius.circular(50)).animate(controller);
    decorationAnimation = DecorationTween(begin: BoxDecoration(color: Colors.purple,borderRadius: BorderRadius.circular(0),),
        end: BoxDecoration(color: Colors.lightBlueAccent,borderRadius: BorderRadius.circular(40))).animate(controller);
    textStyleAnimation = TextStyleTween(begin: TextStyle(color: Colors.black,fontSize: 20,fontWeight: FontWeight.w100),
        end: TextStyle(color: Colors.purple,fontSize: 30,fontWeight: FontWeight.w700)).animate(controller);
    // Start animation
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(title: Text("Tween animation"),),
      body: Container(
        alignment: Alignment.center,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            SizedBox(
              height: 200,
              child:  Container(
                height: doubleAnimation.value,
                width: doubleAnimation.value,
                child: FlutterLogo(),
              ),
            ),
            Container(
              margin: EdgeInsets.only(left: offsetAnimation.value.dx),
              width: 50,
              height: 50,
              color: Colors.green,
            ),
            Container(
              height: 100,
              width: 100,
              color: colorAnimation.value,
            ),
            SizedBox(height: 10,),
            Container(
              height: 100,
              width: 100,
              decoration: BoxDecoration(borderRadius: radiusAnimation.value,color: Colors.blue),
            ),
            Container(
              height: 60,
              width: 200,
              decoration: decorationAnimation.value,
            ),
            Container(
              height: 100,
              child: Text("TestStyleTween",style: textStyleAnimation.value,),
            ),

          ],
        ),
      )
    );
  }
  
  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose(); controller.dispose(); }}Copy the code
Tween animation execution renderings

When we have a complex layout in state, we call setState to refresh the widget tree with every frame change, redrawing all the widgets in state and causing unnecessary performance drain. We only need to refresh the widget where the animation is performed. Flutter provides us with animatedWidgets.

AnimatedWidget

The source code
abstract class AnimatedWidget extends StatefulWidget {
	// Create a widget to refactor when ListEnable changes
  const AnimatedWidget({
    Key key,
    @required this.listenable,
  }) : assert(listenable ! =null),
       super(key: key);
  // Declare a Listenable, frame animation listener
  final Listenable listenable;

  @protected
  Widget build(BuildContext context);

  /// Subclasses typically do not override this method.
  @override_AnimatedState createState() => _AnimatedState(); . }Copy the code

The AnimatedWidget inherits its state from the StatefulWidget and instantiates a ListEnable to listen for frame animation and refresh the AnimatedWidget when the animation method changes.

The source code for the __AnimatedState method is as follows:

class _AnimatedState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    // Add a listening callback
    widget.listenable.addListener(_handleChange);
  }

  @override
  void didUpdateWidget(AnimatedWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.listenable != oldWidget.listenable) {
      oldWidget.listenable.removeListener(_handleChange);
      widget.listenable.addListener(_handleChange);
    }
  }

  @override
  void dispose() {
    widget.listenable.removeListener(_handleChange);
    super.dispose();
  }
	// Trigger a refresh when the frame animation changes
  void _handleChange() {
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }
// Call the build() method to reconstruct the AnimatedWidget
  @override
  Widget build(BuildContext context) => widget.build(context);
}
Copy the code

When the Refresh is triggered by ListEnable, setState is called to refactor the AnimatedWidget, although setState is still called in the end, but the refreshed object is different.

Listenable

From the source code, you can see that a Listenable is declared in the AnimatedWidget to listen for changes to each frame. So what is Listenable. We can take a look at the Animation source code:

abstract class Listenable {

  const Listenable();

  factory Listenable.merge(List<Listenable> listenables) = _MergingListenable;

  void addListener(VoidCallback listener);

  void removeListener(VoidCallback listener);
}
Copy the code

So Animation is also a Listenable implementation class. Read the source code, the specific usage is as follows:

ColorAnimationWidget

Declare a ColorAnimationWidget class that inherits from AnimatedWidget. The code is as follows:

class ColorAnimationWidget extends AnimatedWidget{

  ColorAnimationWidget({Key key, Animation<Color> animation})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final Animation<Color> animation = listenable;
    // TODO: implement build
    return Center(
      child: Container(
        width: 200,
        height: 200, color: animation.value, ), ); }}Copy the code
Using ColorAnimationWidget
class PageState extends State<HomePage> with SingleTickerProviderStateMixin{

  AnimationController _controller;
  Animation<Color> _animation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller = AnimationController(vsync: this,duration: Duration(seconds: 2)); _animation = ColorTween(begin: Colors.lightBlueAccent,end: Colors.red).animate(_controller) .. addStatusListener((AnimationStatus status){if(status == AnimationStatus.completed){
          _controller.reverse();
        }else if(status == AnimationStatus.dismissed){ _controller.forward(); }}); _controller.forward(); }@override
  Widget build(BuildContext context) {
    // TODO: implement build
    returnScaffold( body: ColorAnimationWidget(animation: _animation,), ); }}class ColorAnimationWidget extends AnimatedWidget{

  ColorAnimationWidget({Key key, Animation<Color> animation})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final Animation<Color> animation = listenable;
    // TODO: implement build
    return Center(
      child: Container(
        width: 200,
        height: 200, color: animation.value, ), ); }}Copy the code

You can see that the usage is basically the same as without the AnimatedWidget, with the only difference being that setState is called within the AnimatedWidget and only one widget is refreshed.

ColorAnimationWidget

CurvedAnimation

By default, Tween animation provides linear changes within the interval. If we need curve changes, we need to use CurvedAnimation together. The elastic effect is shown below:

class HomePageState extends State<HomePage> with TickerProviderStateMixin{

  Animation animation;
  AnimationController controller;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    controller = new AnimationController(vsync: this,duration: Duration(seconds: 2));
    CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
    animation = Tween<double>(begin: 0.0,end: 500).animate(curve) .. addListener((){ setState(() { }); }); controller.forward(); }@override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      alignment: Alignment.topCenter,
      child: Container(
        margin: EdgeInsets.only(top: animation.value),
        width: 100,
        height: 100, child: FlutterLogo(), ), ); }}Copy the code

CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.bounceOut); Here we use the bounceOut effect

For more Curves, see the Curves on the Curves website

Let’s look at how bounceOut is implemented:

class _BounceOutCurve extends Curve {
  const _BounceOutCurve._();

  @override
  double transformInternal(double t) {
    return_bounce(t); }}double _bounce(double t) {
  if (t < 1.0 / 2.75) {
    return 7.5625 * t * t;
  } else if (t < 2 / 2.75) {
    t -= 1.5 / 2.75;
    return 7.5625 * t * t + 0.75;
  } else if (t < 2.5 / 2.75) {
    t -= 2.25 / 2.75;
    return 7.5625 * t * t + 0.9375;
  }
  t -= 2.625 / 2.75;
  return 7.5625 * t * t + 0.984375;
}
Copy the code

BounceOut inherits Curve, implements its transformInternal method, and implements its trajectory in transformInternal.

AnimatedBuilder

We implemented a color change example above. What if we now need to implement a size change widget? Do you want to extend the AnimationWidget after declaring a SizeAnimationWidget?

Obviously it’s ok to do that. Better yet, use AnimatedBuilder to refactor our widget.

What is a AnimatedBuilder

The AnimatedBuilder inherits from the abstract AnimationWidget in order to build a generic AnimationWidget implementation class without having to create an implementation class each time an AnimationWidget is used.

AnimatedBuilder source code:
class AnimatedBuilder extends AnimatedWidget {
  /// Creates an animated builder.
  const AnimatedBuilder({
    Key key,
    @required Listenable animation,
    @required this.builder,
    this.child,
  }) : assert(animation ! =null),
       assert(builder ! =null),
       super(key: key, listenable: animation);

  final TransitionBuilder builder;

  final Widget child;

  @override
  Widget build(BuildContext context) {
    returnbuilder(context, child); }}Copy the code

All we need to do is pass in the Animation and Builder

usage
class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin {
  Animation animation;
  AnimationController controller;

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    final CurvedAnimation curve =
    new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
    animation = new Tween(begin: 0.0, end: 300.0).animate(curve);
    controller.forward();
  }

  Widget build(BuildContext context) {
    return new GrowTransition(child: new LogoWidget(), animation: animation);
  }

  dispose() {
    controller.dispose();
    super.dispose(); }}class GrowTransition extends StatelessWidget {
  GrowTransition({this.child, this.animation});

  final Widget child;
  final Animation<double> animation;

  Widget build(BuildContext context) {
    return new Center(
      child: new AnimatedBuilder(
          animation: animation,
          builder: (BuildContext context, Widget child) {
            return newContainer( height: animation.value, width: animation.value, child: child); }, child: child), ); }}class LogoWidget extends StatelessWidget {
  // Leave out the height and width so it fills the animating parent
  build(BuildContext context) {
    return new Container(
      margin: new EdgeInsets.symmetric(vertical: 10.0),
      child: newFlutterLogo(), ); }}Copy the code
rendering

conclusion

This is the end of the Tween animation, Hero animation left in the supplement

Reference: The Flutter animation tutorial