美文网首页
五、Flutter之动画

五、Flutter之动画

作者: 夏_Leon | 来源:发表于2019-02-27 14:56 被阅读0次

    主要参考:https://flutterchina.club/animations/ ,这里有很系统的讲解,要深入理解flutter的动画,务必顺着教程,从Animation、AnimationController、Tween、动画监听等看下来。

    我们直接从封装比较方便的AnimatedWidget开始学习demo快速上手:

    1、线性变化

    //放大动画的封装组件
    class Animation1 extends StatefulWidget {
      _Animation1State createState() => new _Animation1State();
    }
    
    class _Animation1State extends State<Animation1>
        with SingleTickerProviderStateMixin {
      AnimationController controller; //动画控制器,设置动画运行时间和值的计算规则
      Animation<double> animation; //动画值,须传递给动画图标,动画之所以动就是因为该值在变化
    
      initState() {
        super.initState();
        //必须传入动画时间和vsync,存在vsync时会防止屏幕外动画消耗不必要的资源
        controller = new AnimationController(
            duration: const Duration(milliseconds: 2000), vsync: this);
        //animation值取0~300之间,通过Tween来映射生成
        animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
        //启动动画
        controller.forward();
      }
    
      Widget build(BuildContext context) {
        //把动画值传入动画图标EnlargeLogo,当动画启动后,该图标就会根据动画值的变化而变化
        return new EnlargeLogo(animation: animation);
      }
    
      dispose() {
        //组件销毁时记得销毁动画
        controller.dispose();
        super.dispose();
      }
    }
    
    //放大动画图标,本质就是一张图片,通过动画控制器修改height和width属性来达到放大的动画
    class EnlargeLogo extends AnimatedWidget {
      EnlargeLogo({Key key, Animation<double> animation})
          : super(key: key, listenable: animation);
    
      Widget build(BuildContext context) {
        //传入的动画值
        final Animation<double> animation = listenable;
    
        return new Center(
          child: new Container(
            margin: new EdgeInsets.symmetric(vertical: 10.0),
            height: animation.value,
            width: animation.value,
            child: new FlutterLogo(), //FlutterLogo
          ),
        );
      }
    }
    

    上面这段代码实现了一个图标在2s内逐步放大的过程,重要代码都有注释。初学阶段我们从最底层的动画实现开始,自己去分解、实现动画。

    动画分解开来就是一个图形的坐标值、颜色、透明度等在变化,如demo是一个放大动画,就是图标的height、width属性在变大,那我们就可以把动画分解成两步:
    1、长宽值在指定时间内按规律地增大,这里不涉及任何UI,就是单纯的数值增大;
    2、把这个值应用到图标的height、width上,值变化的时候图标就跟随变化(这里的图标必然是个StatefulWidget了)。

    以上面的demo为例,Animation<double> animation 就是逐渐增大的值,它还可以是其他格式像Animation<Color>等,我们要把它传给图标EnlargeLogo,设为图标的长宽值。
    AnimationController controller 是动画控制器,是一个特殊的Animation对象,在指定Duration中线性生成01.0的值,也可以手动设置lowerBound和upperBound,比如设为00.5。
    new Tween(begin: 0.0, end: 300.0).animate(controller),Tween也可以设置计算边界begin和end,并把这种变化规律映射给Animation。

    2、监听animation值及状态

    更进一步的,我们可以对animation值进行监听,只要值发生变化就调用;也可以对status进行监听,在动画结束后反转动画,在反转结束后再播放放大动画。

    animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
          //..是级联符号,用上个表达式的返回值继续调用
          //添加对animation值的监听
          ..addListener(() {
            print(animation.value);
          })
          //添加状态监听放大结束后反转,缩小结束后正放。
          ..addStatusListener((status) {
            print(status);
            if (status == AnimationStatus.completed) {
              controller.reverse(); //翻转动画
            } else if (status == AnimationStatus.dismissed) {
              controller.forward(); //翻转动画
            }
          });
    

    3、实现非线性的变化

    CurvedAnimation类可以将动画过程定义为一个非线性曲线,Curves.fastOutSlowIn属性表示曲线为快速开始慢速结束的,使用如下:

       //非线性曲线
        CurvedAnimation curve =
        new CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
    
        //animation值取0~300之间,通过Tween来映射生成
        animation = new Tween(begin: 0.0, end: 300.0).animate(curve)
    

    就在controller外再包一层变为非线性的,把CurvedAnimation映射给animation就行。动画的启动翻转等还是通过controller来操作。

    4、AnimatedBuilder

    教程中还有AnimatedBuilder重构,为什么要用AnimatedBuilder呢?按我的理解就是进一步解耦,把动画(单纯指动作)和组件(例子中的logo图标)分离开来,让动画可以方便复用在其他组件上。

    AnimatedBuilder是继承自AnimatedWidget,原代码中的EnlargeLogo也是AnimatedWidget,所以AnimatedBuilder其实就是重构替代了AnimatedWidget,把class EnlargeLogo代码块替换为如下代码即可,其他像animation、controller的计算、启动都无需修改,就完成了AnimatedBuilder的重构。

    //固定的图标组件,不在组件内设置宽高等
    class SecondLogo extends StatelessWidget {
      Widget build(BuildContext context) {
        return Container(
          //图标充满父组件
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.height,
          child: FlutterLogo(), //FlutterLogo
        );
      }
    }
    
    //放大动画,把需要进行动画的组件传入child,专注于动画过程,与图标组件解耦,返回的是已经套用过动画的组件
    class EnlargeLogo extends StatelessWidget {
      EnlargeLogo2({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 new Container(
                  height: animation.value,
                  width: animation.value,
                  child: child,
                );
              },
              child: child),
        );
      }
    }
    

    仔细看AnimatedBuilder的源码,其实就是一个内置了animation的AnimatedWidget,和我们一开始的实现过程很像,反而在图标组件外多套一层Builder,除非项目中有大量复用该动画的场景,否则没必要使用AnimatedBuilder,用AnimatedWidget更灵活容易理解。

    5、并行动画

    一般来说并行动画就是给它设置两套动画就行,但是AnimatedWidget的构造函数只接受一个Animation,那我们就只能在AnimatedWidget内部,去定义两套Tween,用不同的规则把同个Animation映射给对应的属性赋值。

    //并行动画的父组件
    class Animation2 extends StatefulWidget {
      _Animation2State createState() => new _Animation2State();
    }
    
    class _Animation2State extends State<Animation2> with TickerProviderStateMixin {
      AnimationController controller;
      Animation<double> animation;
    
      initState() {
        super.initState();
        controller = new AnimationController(
            duration: const Duration(milliseconds: 2000), vsync: this);
        animation = new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
    
        //实现动画循环播放
        animation.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            controller.reverse();
          } else if (status == AnimationStatus.dismissed) {
            controller.forward();
          }
        });
    
        controller.forward();
      }
    
      Widget build(BuildContext context) {
        return new TwoAnimationWidget(animation: animation);
      }
    
      dispose() {
        controller.dispose();
        super.dispose();
      }
    }
    
    //两个并行动画组件,Tween的绑定从父组件挪到子组件中,利用Tween.evaluate,设置不同的Tween把同个animation映射给不同属性
    class TwoAnimationWidget extends AnimatedWidget {
    
      //通过opacityAnimation.value来获取不透明度
      static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
      //通过sizeAnimation.value来获取大小
      static final _sizeTween = new Tween<double>(begin: 0.0, end: 300.0);
    
      TwoAnimationWidget({Key key, Animation<double> animation})
          : super(key: key, listenable: animation);
    
      Widget build(BuildContext context) {
        final Animation<double> animation = listenable;
        return new Center(
          child: new Opacity(
            opacity: _opacityTween.evaluate(animation),
            child: new Container(
              margin: new EdgeInsets.symmetric(vertical: 10.0),
              height: _sizeTween.evaluate(animation),
              width: _sizeTween.evaluate(animation),
              child: new FlutterLogo(),
            ),
          ),
        );
      }
    }
    

    6、Transition类:SizeTransition、FadeTransition、ScaleTransition、AlignTransition、RotationTransition、SlideTransition...

    去官方API搜索Transition会发现一大堆已经封装好的Transition,常见的缩放、渐隐渐现、旋转等都有,Transition也是继承自AnimatedWidget,就相当于一个父容器,在你需要实现动画的组件外包一层Transition组件,并传入需要的Animation对象即可,controller同上面代码:

    /**
     * Transition类
     */
    class Animation3 extends StatefulWidget {
      _Animation3State createState() => new _Animation3State();
    }
    
    class _Animation3State extends State<Animation3> with TickerProviderStateMixin {
      AnimationController controller;
      Animation<double> animation;
    
      initState() {
        super.initState();
        controller = new AnimationController(
            duration: const Duration(milliseconds: 2000), vsync: this);
        animation = new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
    
        //实现动画循环播放
        animation.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            controller.reverse();
          } else if (status == AnimationStatus.dismissed) {
            controller.forward();
          }
        });
    
        controller.forward();
      }
    
      Widget build(BuildContext context) {
        //包裹缩放动画
        return  ScaleTransition(
          scale: animation,
          //包裹旋转动画
          child: RotationTransition(
            turns: animation,
            child: Center(
              child: Text('ScaleTransition + RotationTransition'),
            ),
          ),
        );
    
      }
    
      dispose() {
        controller.dispose();
        super.dispose();
      }
    }
    

    7、其他

    以上是根据中文教程的学习笔记,看到别人写的demo,感觉这种写法更舒服,学习 Flutter知识点: Animation

    相关文章

      网友评论

          本文标题:五、Flutter之动画

          本文链接:https://www.haomeiwen.com/subject/rogayqtx.html