深入理解Flutter动画体系

Flutter的动画体系是怎么运作的,各组件之间的关联关系及原理什么,隐式动画、显式动画怎么区分,本文将会进行详细解答。
将会按照以下顺序进行介绍:

  • 1.Flutter动画基本概念
  • 2.隐式动画&显式动画
  • 3.选择适合自己的动画
  • 4.动画类型
  • 5.常见动画模式

Flutter动画基本概念

什么是动画

1motion.gif

讲解动画之前,需要先介绍一下帧的概念,上面我们看到的小视频,其实是由一张张连续的静止图片所构成,每一幅图片就是一帧。

2motion.png

传统电影每秒播放24帧,现在的手机每秒刷新60到120帧,我们在手机上看到的其实也是每秒刷新的图像。

3光速.gif

这个小视频演示了从0变到光速的动画,利用的也是视觉差,假如手机帧率是每秒60帧,那么你看到的动画,其实是由渲染出来的60次图像所构成。那么,在Flutter系统中动画是怎么形成的呐?

Ticker

首先介绍Ticker,它是Flutter中动画运行的基础。Ticker是一个每帧都会执行某个函数的对象,借助于此,我们可以在每帧的回调中连续改变UI视图的形态,这样视觉上看到的就是连续运行的动画了。

// 创建ticker
var ticker = Ticker((elapsed) => print(‘hello'));

创建完之后,需要手动开启ticker.start(),使用完之后还需要手动释放资源ticker.dispose(),此外还有muted(bool value)、stop()等操作,管理起来比较麻烦,容易疏忽。好消息是99%d的场景你并不会直接使用Ticker,但是在动画中Ticker又是必不可少的,为了方便使用,系统提供了SingleTickerProviderStateMixin来方便开发者,它继承自TickerProvider,实现了createTicker方法。

mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
  Ticker _ticker;
  @override
  Ticker createTicker(TickerCallback onTick) {/*...*/}
  /*...*/
}

这样当我们混入SingleTickerProviderStateMixin之后,自身的类就具备了创建createTicker的能力。

5this.png

AnimationController

接着上图讲解,我们看到创建AnimationControllervsync参数传入this参数,这是因为,我们混入了mixin之后具备了相应的能力,这块的详细解释可以看之前文章深入理解Flutter动画,传入this之后做了什么事情呐?

7animationController.png

我们可以看到,this具备了createTicker能力后,通过传入一个回调函数,就可以创建ticker了,再看回调函数_tick做了哪些事情呐?

8_tick.png

主要有三件事:

  • 根据创建动画传入的最大最小值(默认是0到1)每帧生成改变所对应的值。
  • 通知值得监听者
  • 改变动画状态
    这样创建好的AnimationController就具备了每帧刷新double值的能力,这里强调的是double值,你或许会好奇,我们的动画可能会改变color、rect、position、aligment,这和double值有什么关系呐?这之间的关系稍后会详细讲述,这里先把controller讲述完。

AnimationController和它的名字一致是一个控制器,主要作用是控制动画、驱动动画,其核心是通过控制value变量的值来驱动动画,这点可以很容易通过其Api看出来,几乎所有参数都是double类型。

10AnimationController能力.png

动画正常执行forward,或者翻转reverse通过_animateToInternal(upperBound)_animateToInternal(lowerBound),注意其参数,即是动画改变范围的最小值,最大值。其他操作repeat重复动画则是反复的从最小值到最大值之间变换,动画重置Reset是讲value值重置为初始值。

回到之前1秒从0变光速的动画的例子,我们就可以通过AnimationController来实现

10AnimationController例子.png
3光速.gif

关于AnimationController基本就讲完了,其核心就是每帧改变一个double类型的值,还有改变动画状态,通知值得监听者,后面两个动作就涉及到了AnimationController的父类Animation。通过下图可以看到其继承自Animation类,Animation泛型类型传入的也是double类型的值。

9doubleanimation.png

Animation

最简单的动画或许只需要改变值就可以改变UI效果了,但是如果你想要监听动画是否运行完成,当前运行到哪种程度了,那么只有一个值是不够的,这也是Animation<T>设计的目的,

11animation.png
abstract class Animation<T> extends Listenable implements ValueListenable<T>

Animation就是结合了值、状态、并且能够提供回调的抽象类。因此看到需要传Animation<T>类型的参数,需要使用其子类对象作为参数,它的子类常用的主要有两个AnimationControllerCurvedAnimation

12RotationTransition.png
13animation子类.png

Animation抽象类中另一个比较重要的方法就是drive了,通过绑定一个具有Animatable类型的对象来进行动画值的类型转换,这就是前面的疑问,怎么将AnimationController中的double值转换为想要的类型,比如color、rect、position、aligment等。

Tween(Animatable)

Animatable也是一个抽象类,大部分子类都是Tween类型的,也有一些不是比如CurveTween,涉及到Curve的类有些特殊,稍后会讲到。

15animatable.png
16tween.png

常见的Tween子类有ColorTween、SizeTween、RectTween、CurveTween、StepTween、IntTween等,这里以ColorTween为例讲解一下转换过程。

  • animation可以用来调用drive,返回值是Animation类型的,一般是由AnimationController来调用,传入一个Animatable类型的参数。
  • 假设这个Animatable类型的参数,类型是ColorTween,它复写了父类Tween的lerp函数,当父类lerp被调用时,它将会被调用。
  • ColorTween的真正实现,交给了具体值所在的类,这里是Color,传入三个参数,动画初始值begin和终点值end,此外还有一个double t
  • t来自AnimationController的double值,根据转化公式,即可将动画控制double值转化为目标color值。
17转换值.png

将tween和controller关联起来有两种方式,一种是刚才提到的通过调用Animation的drive方法,另一种是Animatable类中的animate方法,返回值都是Animation类型的,无论哪种方式都离不开AnimationController(提供的原始double值)。如下图

  • controller.drive(tween)
  • tween.anmation(controller)
18tween关联animation.png

多个tween还可以组合在一起,这样可以控制的动画属性就更多了,也有两种方式,通过chain或者drive组合。

19多个tween.png

Curve

除了通过tween对值类型进行改变,我们还可以通过Curve来控制动画执行的速度,常见的curveCurves.easeIn、Curves.easeOut等都是基于Cubic的不同参数对应的实例。系统内置了几十种不同的Cubic实例,这也是curve最常用的控制,如果这些都不能满足用户,你也可以自己基于cubic创建。Cubic支持4个参数,Cubic(a, b, c, d),这个几个参数最终通过一系列公式,转化controller初始double t值得改变顺序,从而实现控制动画执行的速度。

20curve.png
20三阶贝塞尔曲线.png

Cubic的本质其实是一个三阶贝塞尔曲线

这里有一个在线调试cubic的网站,支持实时观看、对比效果。

24cubic-bezier.png
Curves.png

需要注意的一点是cubic计算出来的值有可能为负,小心动画值为负导致约束越界。

Curve的使用方式有三种:作为属性通过Curvetween通过CurveAnimation,无论哪种都离不开 Controller提供的初始值。如下图:

21curve属性.png
19多个tween.png
22curveAnimation.png

基本概念小结

Ticker逐帧给我们提供了回调,AnimationController给我们默认提供了从0到1的值改变,可以用来进行动画控制,基于此初始值,我们可以使用Tween对初始值进行改变,使用Curve对动画速度进行控制。动画的值被包装成Animation类型,为我们提供了值和状态的监听。

隐式动画&显式动画

还记得上面的从0到光速的动画吗?那个只是简单的示范,其实有更简单的写法,甚至都不用自己创建AnimationController,讲完这一节关于隐式、显式动画你就明白啦。隐式动画和显式动画的区分是:是否需要自己创建管理AnimationController,还可以进一步细分为内置隐式动画,自定义隐式动画,内置显式动画,自定义显式动画。隐式动画可以做的,显式动画都可以实现,隐式动画只能控制durationcurve不需要创建controller,简单易用,显式动画则有更多的控制权,在下面分别介绍后,会详细列出它们之间的对比。

隐式动画

常见的隐式动画都是以AnimatedFoo命名的,Foo是没有动画时Widget的名字,系统提供了很多隐式动画,如下图。当下面的组件不满足时,也可以使用TweenAnimationBuilder进行自定义隐式动画,相应的系统提供的隐式动画被称为内置隐式动画

27隐式动画.png

AnimatedFoo第一次加载到Widget树中是没有动画的,思考下为什么?因为隐式动画是一次性的,只有每次当动画值改变时才有动画

28AnimatedAlign.png

以AnimatedAlign为例,其余隐式动画也一样,我们只需要关注属性的值变化、curve、duration
隐式动画大部分集成自ImplicitlyAnimatedWidget类,但是也有一些特殊自接继承自SingleChildRenderObjectWidget,这里也是个设计相关的问题,没有官方答案,自行研究完源码后欢迎一起探讨。隐式动画并不是不需要创建AnimationController,之所以被称之为隐式动画,是因为,创建controller这一步,隐式动画在内部帮助我们创建了,如下图,因此此类动画使用起来更加简便。

29ImplicitlyAnimatedWidget.png

如果系统提供的内置隐式动画不满足需求,可以基于TweenAnimationBuilder进行自定义,看下面示例

31自定义隐式动画示例.png

此示例涉及3个知识点

  • Tween的值可以动态改变,谨记TweenAnimationBuilder永远是从当前值运动到新的终值
  • builder即是用户自定义想要实现的内容
  • child参数放置不需要变的元素,比如自定义Widget中的icon,它会在builder构造中被当成参数传递进去,这块是系统为了优化性能所做的设计。

显式动画

显示动画需要自己创建并管理,系统也提供了一些实现好的显示动画,以FooTransitionFoo是没有动画时Widget的名字

32显式动画.png

大部分显示动画继承自AnimatedWidget,和隐式动画类似,也有一部分直接继承自SingleChildRenderObjectWidget,比如FadeTransition,我的理解是此类Widget动画的改变,只需要直接改变渲染层即可,不涉及到Widget树的改变,你的理解是什么呐?

AnimatedAlign为例,可以和上面介绍的AnimatedAlign对比起来观看,如下图,可以发现,隐式动画需要的参数是真正的值AlignmentGeometry alignment,显示动画的参数变成了Animation<T>类型Animation<AlignmentGeometry> alignment,结合前面介绍的基本概念,这里可以将我们自定义的AnimationController当做参数传进去,因为它也继承自Animation

33AnimatedAlign.png
34AlignTransition.png

如果系统内置的显式动画不满足需求,我们可以使用AnimationBuilder自定义显式动画,AnimationBuilder继承自AnimatedWidget,因此我们也可以直接继承自AnimatedWidget,自定义显示动画关注点和自定义隐式动画类似,同样只需要关注animation、builder、child,其中animation即为自己创建的controller,builder为自定义Widget,child作用和上面一样用来优化性能。

34AnimatedBuilder.png

自定义显式动画两种方式效果一样,建议是直接继承自AnimatedWidget定义单独的Widget,这样更独立,也方便以后复用。当然如果父节点比较简单时,首选AnimatedBuilder。下面有两个小示例

35builder自定义显示Widget.png
34AnimatedBuilder.png

隐式动画&显式动画小结

对比 命名 控制器 值类型 父类 自定义 难易程度
显式动画 FooTransition 显式创建AnimationController,完整的控制权 Animation<T> value 大多数继承自AnimatedWidget 使用AnimatedBuilder或继承自AnimatedWidget 中等
隐式动画 AnimatedFoo 隐式创建Controller,只能控制duration、curve T value 大多数继承自ImplicitlyAnimatedWidget 使用TweenAnimationBuilder 简单

选择适合自己的动画

上面介绍了内置隐式动画、自定义隐式动画,内置显式动画、自定义显式动画,在Flutter动画体系中还有其他类型,那么我们该如何选择使用哪种呐?

  • 隐式动画可以做的,显式动画都可以实现,只是实现难易程度不一样
  • 隐式动画只能控制duration和curve,不需要创建controller,简单易用,显式动画有更多的控制权
25easy2hard.png

此外这里有一张翻译的图供你参考,更详细的介绍之前有翻译过一篇文章如何选择适合您的的Flutter Animation Widget,这里介绍的更详细。

26whichanimation.png

动画类型

动画类型可以分为两大类

常见动画模式

  • 列表和网格动画,常见的ListView,GridView展示,加载、删除的动画
  • 共享元素动画(Hero),标准Hero动画和径向Hero动画
  • 交织动画

标准Hero动画使用起来比较简单,系统提供有heroWidget,只需要在转场前后页面保持同样的tag即可。

其原理是,系统在动画运行的时候,在原视图的基础上覆盖一层overlay,当然还有其中过渡动画。

hero-transition.png

径向Hero动画稍微复杂一些,先看下效果展示,共享元素代码示例

Hero动画.gif
radial-hero-animation.png

这部分的详细介绍看这里Hero动画,文章已经太长了,这里就不展开讲了。

交织动画主要考虑的是:

  • 一个交织动画由一组序列动画或重叠动画所组成。
  • 创建一个交织动画,要用到多个动画对象
  • 一个 AnimationController 控制所有动画。无论动画在真实时间中播放多长时间,控制器的值必须在 0.0 和 1.0 之间,包括 0.0 和 1.0。
  • 每个动画对象在一个间隔时间内指定一个动画。
  • 为每一个要执行动画的属性创建一个 Tween
    这里也不展开细讲,详细介绍可以看这里交织动画
    下面是运行效果及设计图,下面动画源码交织动画示例
交织动画.gif

全文小结

文章第一部分先介绍了一些基本概念TickerAnimationControllerAnimationTweenCurve这些是Flutter动画的核心,通过对其源码分析,了解到彼此间的关联关系。然后介绍了隐式动画和显示动画,以及如何进行自定义,接着又介绍了如何选用适合自己的动画,这部分之前文章有介绍,这里一笔带过了,建议详细阅读下之前的文章,最后介绍了Hero动画和交织动画。

看到这里,想必你对Flutter动画体系有了一定了解,文章中链接的文章,之前已经单开文章介绍过也推荐看一看。当然了解了之后还需要写代码练习,相信再看到Flutter动画代码的时候,就不会那么陌生了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容