美文网首页Flutter圈子
Flutter动画详解(帧与FPS、插值器/估值器、)

Flutter动画详解(帧与FPS、插值器/估值器、)

作者: 奶盖ww | 来源:发表于2020-01-09 14:22 被阅读0次

    原文作者:亢少军
    原文链接:https://mp.weixin.qq.com/s/mIbbkU0TK3FwPHOoiVCShg

    随着技术的发展,很多网页开发技术都带有动画效果,比如淡入淡出、渐变、变大变小,等等。Flutter中的动画效果可以用酷炫来形容,这也是Flutter的一大特色。现代的应用程序不仅仅需要程序稳定、好用,还需要好看,体验好。那么动画效果是必不可少的。

    / 动画概念 /

    动画顾名思义,就是动起来的画面。如果一直持续的动再加上音频那就是我们平时看的电影了。那么画面为什么会动起来了呢?在回答这个问题之前,我们先引入一个概念。

    人眼在观察景物时,光信号传入大脑神经,需经过一段短暂的时间,光的作用结束后,视觉形象并不立即消失,这种残留的视觉称“后像”,视觉的这一现象则被称为“视觉暂留”。

    视觉暂留被认为是电影的最重要的一个理论基础。我们看到的动画,实际上是一连串的画面组成,只不过是以很快的速度去播放,人眼在下一个画面出来之前,还残留着上一个画面的视觉,看起来就像是在没有间隔的播放这一系列的图片,也就是我们称之为的动画。

    帧与FPS

    帧就是影像动画中最小单位的单幅影像画面,一帧就是一副静止的画面。比如我们看到的电影胶片中的每一格即为一帧,电影通常为24帧。

    帧又分为关键帧和过渡帧,如下所示:

    关键帧:相当于二维动画中的原画,指角色或者物体运动或变化中的关键动作所处的那一帧。
    过渡帧:关键帧与关键帧之间的动画可以由软件来创建,叫做过渡帧或者中间帧。

    FPS(Frame Per Second),即每秒显示帧的数量。电影每秒播放24帧,即帧率为24FPS。帧率越大则显示的画面越流畅,动画及视频是同一个原理。

    插值器/估值器

    为了使得动画呈现出丰富的动画效果,就需要使用非线性动画,插值器与估值器可以解决这个问题。概念如下所示:

    插值器:设置属性值从初始值过渡到结束值的变化规律,如匀速、加速及减速等等。即确定了动画效果变化的模式,如匀速变化、加速变化等等。主要应用于实现非线性运动的动画效果。
    估值器:设置属性值从初始值过渡到结束值的变化具体数值。估值器的作用是协助插值器实现非线性运动的动画效果。

    插值器决定值的变化规律(匀速、加速),即决定的是变化趋势,而接下来的具体变化数值则交给估值器。

    如:动画进行了50%(初始值=100,结束值=200 ),那么匀速插值器计算出了当前属性值改变的百分比是50%,那么估值器则负责计算当前属性值 = 100 + (200-100)x50% = 150。

    插值器其实并不复杂,就是一个数学函数,设置属性值从初始值过渡到结束值的变化规律。每个平台都有自己定义好的一系列插值器,可以供开发者选择使用,也提供自定义的接口,本质上是一个贝塞尔函数。

    Flutter中的动画类型

    Flutter中动画分为两类,如下所示:

    补间(Tween)动画:定义开始点、结束点、时间和速度等参数,然后由框架自动计算如何从开始点过度达到结束点。
    基于物理的动画:模拟真实世界的行为。例如,当你掷球时,球在何处落地,取决于抛球速度有多快、球有多重、距离地面有多远。

    / Flutter的动画相关类 /

    首先来看下Flutter的动画基础概念和相关类,如下所示:

    Animation:Flutter中动画的核心类
    AnimationController:动画管理类
    CurvedAnimation:用于定义非线性曲线动画
    Tween:补间对象,用于计算动画使用的数据范围之间的插值。
    Listeners和StatusListeners:用于监听动画状态改变

    Animation介绍

    Flutter中的动画核心类,我们可以理解为Animation是Flutter中动画的基类。它是个抽象类(abstract),所以不能够直接创建其对象来使用动画。Animation具有以下特性:

    Animation对象知道动画的当前状态(例如,它是开始、停止还是向前或向后移动),但它不知道屏幕上显示的内容。
    Flutter中的Animation对象是一个在一段时间内依次生成一个区间之间值的类。Animation对象的输出可以是线性的、曲线的、一个步进函数或者任何其他可以设计的映射。根据Animation对象的控制方式,动画可以反向运行,甚至可以在中间切换方向。
    Animation还可以生成除double之外的其他类型值,如:Animation<Color> 或 Animation<Size>。
    Animation对象有状态,可以通过访问其value属性获取动画的当前值。
    Animation对象本身和UI渲染没有任何关系。

    AnimationController动画管理类

    AnimationController是一个特殊的Animation对象。其继承自Animation ,因此可以在需要Animation对象的任何地方使用它。默认情况下,AnimationController在给定的持续时间内线性生成从0.0到1.0的值。AnimationController在不使用的时候需要dispose,否则会造成资源的泄漏。AnimationController对象创建如下所示:

     AnimationController controller = AnimationController(
          duration: const Duration(milliseconds: 2000),
          vsync: this);
    

    上述是AnimationController 对象的创建方式,构造函数第一个参数是动画执行的时间,单位是毫秒。第二个vsync传入是防止动画离屏之后继续消耗资源。

    vsync对象会绑定动画的定时器到一个可视的Widget,所以当Widget不显示时,动画定时器将会暂停,当Widget再次显示时,动画定时器重新恢复执行,这样就可以避免动画相关UI不在当前屏幕时消耗资源。如果要使用自定义的State对象作为vsync时,请包含TickerProviderStateMixin,代码结构大致如下所示:

    class MyApp extends StatefulWidget {
      _AnimationApp createState() => _AnimationApp();
    }
    
    class _AnimationApp extends State<MyApp> with SingleTickerProviderStateMixin {
        //动画实现
    }
    

    这里需要提一下TickerProvider类,它的主要作用是获取每一帧刷新的通知,作用相当于给动画添加了一个动起来的引擎。

    AnimationController 提供了几个常用的方法。

    <!--开始动画,从开始值向结束值-->
    TickerFuture forward({ double from }) {}
    
    <!--开始反向运行此动画-->
    TickerFuture reverse({ double from }) {}
    
    <!--开始执行动画,结束后重新启动-->
    TickerFuture repeat({ double min, double max, Duration period }) {}
    
    <!--使用阻尼效果驱动动画-->
    TickerFuture fling({ double velocity: 1.0 }) {}
    
    <!--停止动画-->
    void stop({ bool canceled: true }) {}
    
    <!--释放此对象使用的资源,此方法调用后不再控制器对象不再可用-->
    void dispose() {}
    

    CurvedAnimation非线性动画

    CurvedAnimation继承Animation,它将动画过程定义为一个非线性曲线,属于Animation<double>类型。构建其对象的方式如下所示:

    CurvedAnimation curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
    

    构造函数中传入控制器和要执行的曲线方式。Curves类定义了许多常用的曲线,也可以创建自己的,例如我们使用数学函数Math.sin方法构建一个抖动的曲线,代码如下所示:

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

    Flutter定义了一系列的插值器,封装在Curves类中,有下面13种效果:

    linear
    decelerate
    ease
    easeIn
    easeOut
    easeInOut
    fastOutSlowIn
    bounceIn
    bounceOut
    bounceInOut
    elasticIn
    elasticOut
    elasticInOut

    Tween补间值生成类

    AnimationController对象的范围为0.0到1.0。如果需要不同的范围或不同的数据类型,可以使用Tween将动画配置为插入到不同的范围或数据类型。例如,以下Tween从0.0变为500.0:

    Tween doubleTween = Tween<double>(begin: 0.0, end: 500.0);
    

    构造函数传入只需要传入begin和end两个值,当然这里不一定只是double值。

    Tween继承自Animatable<T>,而不是继承自Animation<T>。Animatable与Animation相似,不是必须输出double值。例如,ColorTween指定两种颜色之间的过渡。

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

    要使用Tween对象,请调用其animate()方法,传入一个控制器对象。例如,以下代码在100毫秒内生成从0到200的整数值。

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

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

    Flutter通过抽象类Animatable来实现估值器。Animatable可以根据不同的输入,产出不同的数值。通过重载下面的函数来产生不同的估值器。

    T transform(double t);
    

    它的最主要的子类是Tween,一个线性的估值器,实现如下,非常的简单,就是一个线性函数。

    T lerp(double t) {
      assert(begin != null);
      assert(end != null);
      //返回值 = 开始值 + (结束值 - 开始值) * 传入值
      return begin + (end - begin) * t;
    }
    
    @override
    T transform(double t) {
      //开始
      if (t == 0.0)
        return begin;
      //结束
      if (t == 1.0)
        return end;
      //中间值  
      return lerp(t);
    }
    

    在Tween的基础上实现了不同类型的估值器,如下所示:

    ReverseTween
    ColorTween
    SizeTween
    RectTween
    IntTween
    StepTween
    ConstantTween

    Listeners和StatusListeners动画监听

    Animation对象可以有Listeners和StatusListeners,用addListener来进行动画监听和addStatusListener进行动画状态添加监听。只要动画的值发生变化,就会调用监听器。我们通常可用调用setState以将动画重置状态。动画开始,结束,前进或后退时调用StatusListener,下列是Flutter提供动画的监听方法。

    <!--动画添加监听-->
    void addListener(VoidCallback listener);
    
    <!--动画移除监听-->
    void removeListener(VoidCallback listener);
    
    <!--动画状态添加监听-->
    void addStatusListener(AnimationStatusListener listener);
    
    <!--动画状态移除监听-->  
    void removeStatusListener(AnimationStatusListener listener);
    

    动画状态如下:

    <!--动画状态-->
    enum AnimationStatus {
    
      <!--动画在开始时停止-->   
      dismissed,
    
      <!--动画从开始状态执行到结束状态-->
      forward,
    
     <!--动画反向执行,从结束状态执行到开始状态-->
      reverse,
    
     <!--动画执行完成-->
      completed,
    
    }
    

    动画控制流程

    当我们理解了插值器(Curve)、估值器(Tween)以及Ticker回调的原理。我们就可以理出AnimationController大致的工作流程。

    随着时间的流逝,插值器根据时间产生的值作为输入,提供给估值器,产生动画的实际效果值,结合Ticker的回调,渲染出当前动画值的图像。这也是补间动画的工作原理,如下图所示。


    学习分享,共勉

    题外话,毕竟我在大厂工作多年,深知技术改革和创新的方向,Flutter作为跨平台开发技术、Flutter以其美观、快速、高效、开放等优势迅速俘获人心,但很多FLutter兴趣爱好者进阶学习确实资料,今天我把我搜集和整理的这份学习暂且开放给有需要的人,若有关于此方面可以转发+关注+点赞后领取,或者评论与我一起交流探讨。


    下载地址:https://shimo.im/docs/DR8gWJPrp9XHHkGt

    相关文章

      网友评论

        本文标题:Flutter动画详解(帧与FPS、插值器/估值器、)

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