Flutter 动画 API 简介(Animation API)
本文最后更新于 2021年5月17日 晚上
本文的主要目的是介绍 Flutter 中的动画 API, 相关概念, 类, 以及方法.
Flutter 中的动画主要分为两大类: 插值动画和物理动画.
- 插值动画: 指的是通过设置一个起点和终点, 通过提供的变化曲线进行中间帧插值的一种动画方法.
- 物理动画: 指的是以模拟的方式
为了更好地在 Flutter 中实现动画, 先来看 Flutter 中的常用动画实现模式.
常用动画实现模式
下面是一些常用的动画模式(套路), 便于在日常开发中查阅.
List 或 Grid 动画
这个模式主要用于: List 或 Grid 元素的增删, 详见官方文档.
共享元素的迁移动画
这个模式主要用于: 用户从一个页面选择某元素, 然后将该元素动画迁移到另外一个页面.
在 Flutter 中这样的动画非常容易实现, 使用 Hero
Widget 即可. 另外在官方的文档 中可以找到相关的例子. 另外 Gallery 的 Shrine 里面也有相关的例子.
交错动画
即动画以组合的方式呈现, 类似 iOS 中的 KeyFrame(关键帧)动画.
Flutter 的动画 API 概述
Flutter 中动画系统基于 Animation
对象. Animation 表示一个随动画生命期改变的值, 通过值的变化来映射到 UI 属性的变化. 一般情况下, 可动画的 Widget 会接收一个 Animation
作为参数, 然后自动读取当前值来进行动画.
Animation
对象上可以使用 addListener
添加观察者. 一个非常常用的模式是: State
通过观察 animation 来调用 setState
从而驱动重建.
而这个模式的封装有两个: AnimatedWidget
和 AnimatedBuilder
.
AnimatedWidget
用法: 继承它并重写 build 即可. 且框架中封装了许多常用的动画 Widget, 比如 SlideTransition
就是继承自 AnimatedWidget
的.
AnimatedBuilder
用法: 用在更复杂的场景下, 传入 builder 函数.
可以通过 addStatusListener
添加 Animation
的生命期观察者. 动画的生命期一般情况下从 dismissed
开始, 然后是 forward
(若值从0到1) 或 reverse
(若值从1到0), 然后到结束状态 completed
.
AnimationController
要创建动画, 一般都是先创建 AnimationController
. 有了它之后, 就可以基于它创建更多动画
Tween
插值器, 它可以对任意值计算从 0 到 1 的插值(或者说将 0 到 1 映射为任意的类型值). 比如颜色(使用 ColorTween
), 矩形框(RectTween
)等, 这些类都是继承后重写 lerp
方法实现插值, 要写自定义的类也非常方便.
从源码角度看 Flutter 动画实现原理
接收到引擎的 begin frame 消息后, SchedulerBinding
会执行所有通过 scheduleFrameCallback
注册的帧回调(Transient 回调), 而这些回调中就包含动画注册的回调.
由动画关联的 Ticker
使用 scheduleFrameCallback
来注册回调, 每次 tick 都会注册一次. 这样就保证每帧的内容都不一样, 从而形成动画. 在 Ticker 内部的执行流程如下:
start
-> scheduleTick
(保证只执行一次) -> 注册回调 _tick
-> 在 _tick
中调用 _onTick
并再次 scheduleTick
.
这样循环往复, 直到 stop
或其他终止条件. 其中 _onTick
是外部传入的, 在源码中可以发现, 源码中只有 TickerProvider
及其子类在构造 Ticker
, 且通过 createTicker
方法在构造, 构造 Ticker 时传入 onTick
方法, 对应 _onTick
.
而 createTicker
方法的调用者只有 AnimationController
, 通过 vsync.createTicker(_tick);
进行. 这里传入的 _tick
方法就是整个动画控制的核心, 它会在动画时间结束后停止 ticker, 并修改动画状态, 同时通知动画观察者和动画状态观察者.
所以可知, 动画在使用时: 需要 TickerProvider
, 通过它再创建 AnimationController
通过控制器来控制动画的整个流程, 另外最重要的是 AnimationController
也是 Animation
.
1 |
|
Animatable
的绝大部分子类都是 Tween
族的, 意味着 Tween 可以进行链接, 先应用父的映射, 再应用子的映射, 从而可以实现复杂动画.
Animation
也有类似的父子关系, 但含义有区别: 父子关系意味着孩子是被父驱动的.
动画实践
通过 AnimationController 控制动画, 通过 Curve 提供动画曲线, 通过 Tween 插值.
实现自己的曲线也非常简单:
1 |
|
使用 Tween 的时候, 通过它的 animate 传入一个 Animation 作为入口, 并获取结果 Animation. 一般情况下, 传入的都是 AnimationController, 并且可以在它上面链接多个 Animation, 如下所示:
1 |
|
调用 Tween
的 animate
方法处理后, 从 Animation 拿的 value 就是通过 cureve 再次进行映射计算后的值.
一般来说, 使用插值器的场景是在非 0 到 1 的范围内进行线性/非线性动画的情况下.
最基本的动画套路: 通过 Animation 对象提供动画的当前值, 然后添加观察者, 在观察回调中调用 setState
触发重建. 从而形成动画.
Hero 动画
这种动画表示将一个 Widget 从一个页面传递到另外一个页面的动画. 使用 Hero Widget 即可实现.
有两种 Hero 动画方式: 标准的和径向变化的.
在 API 的使用上, 实际是把两个 Hero Widget 放到两个页面内, 它们拥有相同的 Tag, 从而实现转变动画. 当页面入栈和出栈时, 就会触发 Hero 动画. 在动画过程中, Hero Widget 被转移到一个单独的覆盖层中, 所以它不会被任何页面遮住.
在目前的实现中, 还无法将 Hero 动画到一个 Dialog 中, 不过有解决方案, 详见GitHub issue.
使用 Hero 时, 有可能出现找不到 Material 的情况, 所以此时加一层 Material 包裹即可.
径向变形的 Hero 动画则是在飞行的同时对形状进行改变, 主要通过给 Hero 的 createRectTween
赋值插值器生成函数完成, 因为如果不设置这个插值器的话, 默认使用的是线性变化的插值器.
动画 API 的使用套路总结
根据官方文档摘录.
手动创建动画控制器, 创建插值器并对控制器加工(使用 animate 方法), 也可以多个插值器串联. 在最终的动画对象上添加观察者调用 setState 触发重建, 重建的时候可以是改变某个状态, 或者是直接将动画的值赋值给某个视图属性.
继承
AnimatedWidget
: 它本身是 StatefulWidget, 在构造时传入动画或 changenotifier. 另外框架中有许多这样的 Widget 都是继承自AnimatedWidget
.使用
AnimatedBuilder
封装需要动画的 Widget, 并把不需要动画改变属性的子树从动画 Widget 中隔离, 同时分离动画对象的创建.使用
ImplicitlyAnimatedWidget
的子类进行隐式动画.