美文网首页程序员
Flutter学习笔记31-动画简介

Flutter学习笔记31-动画简介

作者: zombie | 来源:发表于2020-12-17 11:26 被阅读0次

    在任何系统的UI框架中,动画实现的原理都是相同的:在一段时间内,快速地多次改变UI外观;由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画。我们将UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second),即每秒的动画帧数。帧率越高则动画就会越流畅。一般情况下,对于人眼来说,动画帧率超过16FPS,就比较流畅了,超过32FPS就会非常的细腻平滑,而超过32FPS,人眼基本上就感受不到差别了。由于动画的每一帧都是要改变UI输出,所以在一个时间段内连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧率是重要的性能指标,在Flutter中,理想情况下是可以实现60FPS的,这和原生应用能达到的帧率是基本是持平的。
    为了方便开发者创建动画,不同的UI系统对动画都进行了一些抽象。Flutter中也对动画进行了抽象,主要是AnimationCurveControllerTween这四个角色,它们一起配合来完成一个完整动画。

    Animation

    Animation是一个抽象类,它本身和UI渲染没有任何关系,而它主要的功能是保存动画的插值和状态;其中一个比较常用的Animation类是Animation<double>Animation对象是一个在一段时间内依次生成一个区间(Tween)值的类。Animation对象在整个动画执行过程中输出的值可以是线性的、曲线的等等,这由Curve来决定。 根据Animation对象的控制方式,动画可以正向运行(从起始状态开始,到终止状态结束),也可以反向运行,甚至可以在中间切换方向。Animation还可以生成除double之外的其他类型值,如:Animation<Color>Animation<Size>。在动画的每一帧中,都可以通过Animation对象的value属性获取动画的当前状态值。

    可以通过Animation来监听动画每一帧以及执行状态的变化,Animation有如下两个方法:

    • addListener()
      它可以用于给Animation添加帧监听器,在每一帧都会被调用。帧监听器中最常见的行为是改变状态后调用setState()来触发UI重建。
    • addStatusListener()
      它可以给Animation添加“动画状态改变”监听器;动画开始、结束、正向或反向(见AnimationStatus定义)时会调用状态改变的监听器。
      当动画的状态发生变化时,会通知所有通过 addStatusListener 添加的监听器。通常情况下,动画会从dismissed状态开始,表示它处于变化区间的开始点。
      举例来说,从 0.0 到1.0的动画在dismissed状态时的值应该是 0.0。
      动画进行的下一状态可能是forward(比如从 0.0 到 1.0)或者reverse(比如从 1.0 到 0.0)最终,如果动画到达其区间的结束点(比如 1.0),则动画会变成completed状态。
    abstract class Animation<T> extends Listenable implements ValueListenable<T> {
      const Animation();
      
      // 添加动画监听器
      @override
      void addListener(VoidCallback listener);
      
      // 移除动画监听器
      @override
      void removeListener(VoidCallback listener);
        
      // 添加动画状态监听器
      void addStatusListener(AnimationStatusListener listener);
        
      // 移除动画状态监听器
      void removeStatusListener(AnimationStatusListener listener);
        
      // 获取动画当前状态
      AnimationStatus get status;
        
      // 获取动画当前的值
      @override
      T get value;
    

    Curve

    动画过程可以是匀速的、匀加速的或者先加速后减速等。Flutter中通过Curve(曲线)来描述动画过程,通常把匀速动画称为线性的(Curves.linear),而非匀速动画称为非线性的。
    可以通过CurvedAnimation来指定动画的曲线,CurvedAnimationAnimation<double>类型,CurvedAnimation可以将AnimationControllerCurve结合起来,生成一个新的Animation对象:

    class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
      CurvedAnimation({
        // 通常传入一个AnimationController
        @required this.parent,
        // Curve类型的对象
        @required this.curve,
        this.reverseCurve,
      });
    }
    

    Curve类是一个预置的枚举类,定义了许多常用的曲线,具体效果可查阅文档。也可以自定义Curve,定义一个正弦曲线:

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

    AnimationController

    AnimationController用于控制动画,它包含动画的启动forward()、停止stop()反向播放 reverse()等方法。AnimationController会在动画的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内线性的生成从0.0到1.0(默认区间)的数字。 AnimationController的定义:

    class AnimationController extends Animation<double>
      with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
      AnimationController({
        // 初始化值
        double value,
        // 动画执行的时间
        this.duration,
        // 反向动画执行的时间
        this.reverseDuration,
        // 最小值
        this.lowerBound = 0.0,
        // 最大值
        this.upperBound = 1.0,
        // 刷新率ticker的回调
        @required TickerProvider vsync,
      })
    }
    

    AnimationController派生自Animation<double>,因此可以在需要Animation对象的任何地方使用。 AnimationController具有控制动画的其他方法,例如forward()方法可以启动正向动画,reverse()可以启动反向动画。在动画开始执行后开始生成动画帧,屏幕每刷新一次就是一个动画帧,在动画的每一帧,会随着根据动画的曲线来生成当前的动画值(Animation.value),然后根据当前的动画值去构建UI,当所有动画帧依次触发时,动画值会依次改变,所以构建的UI也会依次变化,所以最终我们可以看到一个完成的动画。 另外在动画的每一帧,Animation对象会调用其帧监听器,等动画状态发生改变时(如动画结束)会调用状态改变监听器。duration表示动画执行的时长,通过它我们可以控制动画的速度。
    ps: 在某些情况下,动画值可能会超出AnimationController的[0.0,1.0]的范围,这取决于具体的曲线。也就是说,根据选择的曲线,CurvedAnimation的输出可以具有比输入更大的范围。例如,Curves.elasticIn等弹性曲线会生成大于或小于默认范围的值。

    Ticker

    当创建一个AnimationController时,需要传递一个vsync参数,它接收一个TickerProvider类型的对象,它的主要职责是创建Ticker,定义如下:

    abstract class TickerProvider {
      // 通过一个回调创建一个Ticker
      Ticker createTicker(TickerCallback onTick);
    }
    

    Flutter应用在启动时都会绑定一个SchedulerBinding,通过SchedulerBinding可以给每一次屏幕刷新添加回调,而Ticker就是通过SchedulerBinding来添加屏幕刷新回调,这样一来,每次屏幕刷新都会调用TickerCallback。使用Ticker(而不是Timer)来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源,因为Flutter中屏幕刷新时会通知到绑定的SchedulerBinding,而Ticker是受SchedulerBinding驱动的,由于锁屏后屏幕会停止刷新,所以Ticker就不会再触发。
    通常会将SingleTickerProviderStateMixin添加到State的定义中,然后将State对象作为vsync的值。

    Tween

    默认情况下,AnimationController动画生成的值所在区间是0.0到1.0
    如果希望使用这个以外的值,或者其他的数据类型,就需要使用Tween
    Tween的定义:

    class Tween<T extends dynamic> extends Animatable<T> {
      Tween({ this.begin, this.end });
    }
    

    Tween构造函数需要beginend两个参数。Tween的唯一职责就是定义从输入范围到输出范围的映射。
    Tween继承自Animatable<T>,而不是继承自Animation<T>Animatable中主要定义动画值的映射规则。
    下面是ColorTween将动画输入范围映射为两种颜色值之间过渡输出的例子:

    final Tween colorTween =
        ColorTween(begin: Colors.transparent, end: Colors.black54);
    

    Tween对象不存储任何状态,它提供了evaluate(Animation<double> animation)方法,它可以获取动画当前映射值。 Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其它处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。

    Tween.animate

    要使用Tween对象,需要调用其animate()方法,然后传入一个控制器对象。代码示例:

    final AnimationController controller = AnimationController(
        duration: const Duration(milliseconds: 500), vsync: this);
    Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);
    

    animate()返回的是一个Animation,而不是一个Animatable

    以下代码示例构建了一个控制器、一条曲线和一个Tween:

    final AnimationController controller =  AnimationController(
        duration: const Duration(milliseconds: 500), vsync: this);
    final Animation curve =  
    CurvedAnimation(parent: controller, curve: Curves.easeOut);
    Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);
    

    相关文章

      网友评论

        本文标题:Flutter学习笔记31-动画简介

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