Flutter-动画基本结构及监听状态

91 阅读3分钟

基础动画

class ScaleAnimationRoute extends StatefulWidget {
  const ScaleAnimationRoute({Key? key}) : super(key: key);
  
  @override
  _ScaleAnimationRouteState createState() => _ScaleAnimationRouteState();
}

//需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;
  
  @override
  initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
//使用弹性曲线
    animation=CurvedAnimation(parent: controller, curve: Curves.bounceIn);

    //匀速
    //图片宽高从0变到300
    animation = Tween(begin: 0.0, end: 300.0).animate(controller)
      ..addListener(() {
        setState(() => {});
      });

    //启动动画(正向执行)
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Image.asset(
        "imgs/avatar.png",
        width: animation.value,
        height: animation.value,
      ),
    );
  }
  
  @override
  dispose() {
    //路由销毁时需要释放动画资源
    controller.dispose();
    super.dispose();
  }
}

代码中addListener函数调用了setState,所以每次动画生成一个新的数字时,当前帧被标记为脏(dirty),这会导致widget的build方法再次被调用,在build中,改变Image的宽高,因为它的高度和宽度使用的是animation.value,所以会逐渐放大。需要注意的是,动画完成时要释放控制器(调用dispose()函数)防止内存泄漏。

AnimatedWidget简化

通过addListener和setState来更新UI比较繁琐,AnimatedWidget类封装了调用setState的细节,允许我们将widget分离出来。

import 'package:flutter/material.dart';

class AnimatedImage extends AnimatedWidget {
  const AnimatedImage({
    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: Image.asset(
        "imgs/avatar.png",
        width: animation.value,
        height: animation.value,
      ),
    );
  }
}

class ScaleAnimationRoute1 extends StatefulWidget {
  const ScaleAnimationRoute1({Key? key}) : super(key: key);

  @override
  _ScaleAnimationRouteState createState() =>  _ScaleAnimationRouteState();
}

class _ScaleAnimationRouteState extends State<ScaleAnimationRoute1>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  @override
  initState() {
    super.initState();
    controller =  AnimationController(
        duration: const Duration(seconds: 2), vsync: this);
    //图片宽高从0变到300
    animation =  Tween(begin: 0.0, end: 300.0).animate(controller);
    //启动动画
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedImage(
      animation: animation,
    );
  }

  @override
  dispose() {
    //路由销毁时需要释放动画资源
    controller.dispose();
    super.dispose();
  }
}

用AnimatedBuilder重构

用AnimatedWidget可以从动画中分离出widget,而动画的渲染过程(即设置宽高)仍然在AnimatedWidget中,假设需要再添加一个widget透明度变化的动画,那么需要再实现一个AnimatedWidget,这样很繁琐,如果能把渲染过程也抽离出来,就会很优雅。AnimatedBuilder正式将渲染逻辑分离出来:

@override
Widget build(BuildContext context) {
  //return AnimatedImage(animation: animation,);
    return AnimatedBuilder(
      animation: animation,
      child: Image.asset("imgs/avatar.png"),
      builder: (BuildContext ctx, child) {
        return  Center(
          child: SizedBox(
            height: animation.value,
            width: animation.value,
            child: child,
          ),
        );
      },
    );
}

上面的代码似乎child看起来被指定了两次,实际上是:将外部引用的child传递给AnimatedBuilder后,AnimtedBuilder再将其传递给匿名构造器,然后将该对象作为其子对象。最终结果是AnimatedBuilder返回的对象插入到widget中。

好处:

  1. 不需要显示的去添加帧监听器,然后再调用setState。
  2. 更好的性能,因为每一帧需要构建的widget的范围缩小了,如果没有builder,setState将会在父组件上下文中调用,这会导致父组件的build方法重新调用,而builder之后,只会导致动画widget自身builder重新调用,避免不必要的rebuilder。
  3. 通过AnimatedBuilder可以封装常见的过渡效果来复用动画。
class GrowTransition extends StatelessWidget {
  const GrowTransition({Key? key,
    required this.animation,
    this.child,
  }) : super(key: key);

  final Widget? child;
  final Animation<double> animation;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: animation,
        builder: (BuildContext context, child) {
          return SizedBox(
            height: animation.value,
            width: animation.value,
            child: child,
          );
        },
        child: child,
      ),
    );
  }
}

动画状态监听

Animation的addStatusListener方法添加动画状态改变监听器。Flutter中有四种动画状态:

  • dismissed:动画在起始点停止
  • forward:动画正在正向执行
  • reverse:动画正在反向执行
  • completed:动画在终点停止

示例:

initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(seconds: 1), 
      vsync: this,
    );
    //图片宽高从0变到300
    animation = Tween(begin: 0.0, end: 300.0).animate(controller);
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //动画执行结束时反向执行动画
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        //动画恢复到初始状态时执行动画(正向)
        controller.forward();
      }
    });

    //启动动画(正向)
    controller.forward();
  }