Flutter 掌握动画开发

作者: 晓峰残月 | 来源:发表于2019-09-26 10:17 被阅读0次

    主本文主要说明动画的基本原理和简单的动画的实例

    阅读本文大约需要 6 分钟

    背景

    给UI界面设计合理的动画,可以让用户觉得更加流畅、直观,提高用户的交互使用感受,改善用户体验。

    在 Flutter 中动画分为两类:基于补间 (Tween) 的和基于物理 (Physics) 的;

    补间动画是介于两者之间的简称,在补间动画中定义起点和终点、时间点以及定义时间变化和速度的曲线,然后由系统计算如何从开始点到结束点。

    物理动画是运动被模拟为与真实世界的行为相似,比如抛一件物体,它落在什么地方取决于这个物体的重量,抛出去的速度以及这个物体与地面的高度,类似数学中的抛物线运动轨迹。

    介绍

    在 Flutter 中想要实现动画效果离不开几个核心的角色:Animation(动画对象),AnimationController(动画控制器),Tweens(插值器),Curves(动画曲线);

    1、Animation

    在 Flutter 中动画本身和UI渲染没有任何关系,Animation是一个抽象类,它拥有其当前值和状态(完成或停止),Flutter 中的动画系统就是基于 Animation 对象的。其中比较常用的就是Animation类是Animation<double>。它可以通过其 value 属性来获取当前动画的值。

    Animation 除了可以生成 double 的值之外还可以生成如:颜色--Animation<Color> 或者大小--Animation<Size>

    Animation 对象可以拥有 Listeners 和 StatusListeners 监听器,可以用 addListener()addStatusListener() 来添加。只要动画的的值发生变化,就会调用监听器。正常我们在 Listeners 中调用setState() 来触发UI重建;动画开始、结束、向前移动或向后移动时会调用StatusListener。

    2、AnimationController

    AnimationController 是一个特殊的 Animation 对象,在屏幕刷新的每一帧,就会生成一个新的值。默认情况下,AnimationController 会在特定的时间内线性的生成0.0到1.0的数字。AnimationController派生于 Animation<double>,因此可以在需要Animation对象的任何地方使用。不但如此,AnimationController还具有控制动画的其他方法,比如forward()`方法可以启动动画。

    AnimationController({
        double value,
        this.duration,
        this.reverseDuration,
        this.debugLabel,
        this.lowerBound = 0.0,
        this.upperBound = 1.0,
        this.animationBehavior = AnimationBehavior.normal,
        @required TickerProvider vsync,
      })
    

    创建 AnimationController 必须需传入 vsync,传入 vsunc 是为了防止动画的UI不在当前屏幕时,不需要绘制,从而防止消耗不必要的资源。通过将 SingleTickerProviderStateMixin 混入到类定义中,就可以将 statefu l对象作为 vsync 的值。

    除了 vsync 还可以传入正向动画执行的时间 duration 以及反向动画执行时间 reverseDuration 等。

    常用函数:

    序号 方法 介绍
    1 forward() 开始播放动画
    2 stop() 停止动画
    3 reset() 重制动画
    4 reverse() 反向播放动画,必须处于正向动画播放完成的状态之后才有用
    5 dispose() 释放动画占用资源
    6 repeat() 循环播放动画

    注意:动画完成时释放控制器(调用 dispose() 方法)以防止内存泄漏

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

    3、Tween

    默认情况下,AnimationController对象的范围从0.0到1.0。如果您需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。比如,可以生产从0-100的数字:

    final Tween doubleTween = new Tween<double>(begin: 0.0, end: 100.0);
    

    Tween是一个无状态(stateless)对象,继承自Animatable<T>,而不是继承自Animation<T>。Tween 需要两个值,分别是:begin 和 end。Tween的唯一职责就是定义从输入范围到输出范围的映射。

    Animatable与Animation相似,不是必须输出double值,也可以是颜色,比如,从白色到黑色:

    final Tween colorTween = new ColorTween(begin: Colors.withe, end: Colors.black);
    

    Tween 可以通过 animate() 方法传入 controller 对象创建 Animation 对象。如下

    AnimationController _animationController = AnimationController(animationBehavior:AnimationBehavior.normal,vsync: this);
    Tween<double> _tween = Tween<double>(begin: 0.0, end: 100.0)..animate(_animationController);
    

    4、CurvedAnimation

    Curves 用来调整动画过程中随时间的变化率,默认情况下,动画以均匀的线性模型变化。Flutter 内部也提供了一系列实现相应变化率的 Curves 对象:linea ------ 线性,decelerate ------ 减速等。

    当然,也可以自定义继承 Curves 的类来定义动画的变化率,如:

    class ShakeCurve extends Curve {
      @override
      double transform(double t) {
        return math.sin(t * math.PI * 2);
      }
    }
    

    5、添加监听

    目前为止动画只是实现了自身数值的变化,并没有让 Widget 动起来,这里我们需要对动画数值进行监听,然后使用 setstatus 来更新 Widget 的属性,从而使 Widget 动起来。

    添加数值监听:

    Animation animation = CurvedAnimation(parent: _animationController, curve: Curves.linear);
        animation.addListener((){
          setState(() {
            
          });
        });
    

    除此之外我们还可以监听动画的状态变更,当动画结束时我们反转动画,当动画的反转也结束后我们从新开始动画,这样动画就会一直这样循环下去。

    状态变更监听:

    animation.addStatusListener((status){
          print(status);
        });
    

    6、AnimatedWidget

    AnimatedWidget 类允许您从 setState() 调用中的动画代码中分离出 widget 代码。AnimatedWidget 不需要维护一个 State 对象来保存动画。

    以下代码为官方文档自定义 AnimatedLogo

    import 'package:flutter/animation.dart';
    import 'package:flutter/material.dart';
    
    class AnimatedLogo extends AnimatedWidget {
      AnimatedLogo({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(),
          ),
        );
      }
    }
    
    class LogoApp extends StatefulWidget {
      _LogoAppState createState() => new _LogoAppState();
    }
    
    class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
      AnimationController controller;
      Animation<double> animation;
    
      initState() {
        super.initState();
        controller = new AnimationController(
            duration: const Duration(milliseconds: 2000), vsync: this);
        animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
        controller.forward();
      }
    
      Widget build(BuildContext context) {
        return new AnimatedLogo(animation: animation);
      }
    
      dispose() {
        controller.dispose();
        super.dispose();
      }
    }
    

    AnimatedWidget 为什么不需要维护一个 State 对象来保存动画呢?

    AnimatedWidget 源码中看一看出 AnimatedWidget 是继承自 StatefulWidget 类,在 AnimatedWidget 中,创建 state 是创建了 _AnimatedState,接着看 _AnimatedState 类部分源码:

    abstract class AnimatedWidget extends StatefulWidget{
      
       @override
      _AnimatedState createState() => _AnimatedState();
      
    }
    

    _AnimatedState 类的 initState 方法添加了监听 _handleChange,并在 didUpdateWidgetdispose 方法中移除了,_handleChange 里面只有一行代码就是 setState 方法:

    _AnimatedState 源码

    class _AnimatedState extends State<AnimatedWidget> {
      @override
      void initState() {
        super.initState();
        // 添加监听
        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();
      }
    
      void _handleChange() {
        // 更新状态
        setState(() {
          // The listenable's state is our build state, and it changed already.
        });
      }
    
      @override
      Widget build(BuildContext context) => widget.build(context);
    }
    

    7、并行动画

    所谓的并行动画就是一起执行多个动画,在 Flutter 中可以在同一个动画控制器上使用多个Tween,然后每个Tween管理动画中的不同效果,从而实现多个动画同时执行。

    final AnimationController controller =
        new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
    final Animation<double> sizeAnimation =
        new Tween(begin: 0.0, end: 300.0).animate(controller);
    final Animation<double> opacityAnimation =
        new Tween(begin: 0.1, end: 1.0).animate(controller);
    

    可以通过sizeAnimation.value来获取大小,通过opacityAnimation.value来获取不透明度,但AnimatedWidget的构造函数只接受一个动画对象。 为了解决这个问题,可以创建了自己的Tween对象并显式计算了这些值。

    build方法.evaluate()在父级的动画对象上调用Tween函数以计算所需的sizeopacity值。

    import 'package:flutter/animation.dart';
    import 'package:flutter/material.dart';
    
    class AnimatedLogo extends AnimatedWidget {
      // The Tweens are static because they don't change.
      static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
      static final _sizeTween = new Tween<double>(begin: 0.0, end: 300.0);
    
      AnimatedLogo({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(),
            ),
          ),
        );
      }
    }
    

    实例

    效果图

    1、缩放动画

    直接贴代码

    ///放大缩小动画
      Widget scale() {
        return Column(
          children: <Widget>[
            Container(
              height: 170,
              child: Center(
                child: Container(
                  width: _scaleAnimation.value,
                  height: _scaleAnimation.value,
                  child: new FlutterLogo(),
                ),
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  color: Colors.blue,
                  child: Text(
                    "放大",
                    style: TextStyle(color: Colors.white),
                  ),
                  onPressed: () {
                    _scaleController.forward();
                  },
                ),
                RaisedButton(
                  color: Colors.red,
                  child: Text(
                    "缩小",
                    style: TextStyle(color: Colors.white),
                  ),
                  onPressed: () {
                    _scaleController.reverse();
                  },
                )
              ],
            ),
          ],
        );
      }
    

    2、淡入淡出动画

    代码:

    /// 淡入淡出
      Widget alpha() {
        return Column(
          children: <Widget>[
            Container(
              height: 170,
              child: Center(
                child: Container(
                  height: 100,
                  width: 100,
                  child: Opacity(
                    opacity: _alphaAnimation.value,
                    child: FlutterLogo(),
                  ),
                ),
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  color: Colors.blue,
                  child: Text(
                    "淡入",
                    style: TextStyle(color: Colors.white),
                  ),
                  onPressed: () {
                    _alphaController.forward();
                  },
                ),
                RaisedButton(
                  color: Colors.red,
                  child: Text(
                    "淡出",
                    style: TextStyle(color: Colors.white),
                  ),
                  onPressed: () {
                    _alphaController.reverse();
                  },
                )
              ],
            ),
          ],
        );
      }
    

    注意,一个 Widget 使用多个animationController 需要修改混入SingleTickerProviderStateMixin 为 TickerProviderStateMixin。

    结尾

    完整代码奉上GitHub地址:fluter_demo ,欢迎star和fork。

    到此,本文就结束了,如有不当之处敬请指正,一起学习探讨,谢谢🙏。

    相关文章

      网友评论

        本文标题:Flutter 掌握动画开发

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