前面我们看了 Flutter 官方给出的动画教程,基本可以实现自己想要的动画了,但是有一些点我们似乎没有看到,比如动画是怎么和 SchedulerBinding
帧调度关联起来的,怎么把时间和效果值对应上的等等,这一节就把完整的动画流程梳理清楚。
动画核心类
👉看完之后,你就能告诉别人哪个类实现了Flutter动画 ,这篇文档中我们知道了动画的核心类和动画的框架,总结下来就是:
Animation
定义了动画的接口,当前的值是什么、谁监听了我等等
AnimationController
是Animation
的具体实现,同时也暴漏了给开发者使用的api, 比如开始动画、重复动画、设置动画时长等等
Ticker
是动画的调度器,将动画的流程接入到了绘制流程SchedulerBinding
中
Simulation
是时间与状态的模型,定义了时间与具体值的关系
类的基本结构如下:
Animation
官方文档反复提到,这个类是最核心的类。我们看一下介绍
An animation with a value of type `T`
`Animation` 是可以带有类型的,比如 `double` 类型的数值动画,Color类型的颜色动画等等。
An animation consists of a value (of type `T`) together with a status. The
status indicates whether the animation is conceptually running from
beginning to end or from the end back to the beginning, although the actual
value of the animation might not change monotonically
一组数据和一组状态就构成了动画。数据是 0-1。状态是开始、结束等。
状态表明了 从概念上 动画是否正在运行,从开始到结束、从末尾到开始。
因为动画真正的value,可能不是线性的递减或者递增
Animations also let other objects listen for changes to either their value or their status. These callbacks are called during the "animation" phase of
the pipeline, just prior to rebuilding widgets.
Animations 也允许 其他的对象 监听是否动画的值或者状态 发生了改变。
回调的监听会在 "动画"阶段 被监听,这个阶段是先于重绘的。
To create a new animation that you can run forward and backward, consider using[AnimationController].
可以使用AnimationController来驱动创建一个动画
通过上面的描述,我们可以知道:
动画是有类型的,不仅限于数值
动画是有状态和值的,并且状态和值可以监听到
可以通过AnimationController创建和驱动动画
不同于setState,在绘制线的流程中,动画会先于绘制阶段,将绘制需要用到的值计算好,直接用于绘制
Animation类中有一个属性非常重要,就是
/// The current value of the animation.
@override
T get value;
这个值的含义就是:当前的动画时刻,动画的值是什么
有了这个值我们就可以,根据这个值来决定显示什么内容
动画说白了 在一时间段内,时间的变化和值的变化相互关联。现在可以通过 get value
获取到获取到动画的值,那么值与时间的关系是谁决定的呢?这就是Simulation的作用。
Simulation
从字面理解Simulation就是模拟的意思,模拟的就是时间与动画值的关系。它定义的接口很简单:
double x(double time):给定时间下,数值是多少
double dx(double time):给定时间下,数值的偏移量是多少
bool isDone(double time):给定时间下是否已经完成模拟
这只是接口的定义,我们每次使用动画,包括前面的一些文章中,并没有关注过这些,是因为AnimationController帮我们做了默认的实现。
当我们驱动动画的时候,就会生成这样一个实现。
我们以具体的 x 方法 为例,来看动画的值是怎么计算的。
上面的代码中:
timeInSeconds:给定的时间参数
_durationInSeconds: 转为秒数的总共的时长
t:时间的百分比
上面的计算过程就是这张图的表示,略有不同的是
_begin + (_end - _begin) * t 变为了 _begin + (_end - _begin) * _curve.transform(t)
这就是我们说的动画效果的不同,_curve
是动画效果,比如是线性的,贝塞尔的,抛物线的等等。
不过基本的计算过程就是这样。
现在 Animation
定义了动画接口,Simulation
确定了时间与动画值的关系。
那么时间从哪里来呢?时间的来源就是 Ticker
Ticker
我们知道 Flutter 的绘制任务来自 SchedulerBinding ,同样 Ticker
的驱动也是来自 SchedulerBinding
。
Ticker
描述如下:
Calls its callback once per animation frame.
每一个动画帧 都会调用Ticker的回调。类似于 滴答调一下回调 滴答调一下回调
When created, a ticker is initially disabled. Call [start] to enable the ticker.
当ticker被创建的时候,ticker是不可用的。调用了 start方法之后,ticker才会可用
Tickers are driven by the [SchedulerBinding].
Tickers是被SchedulerBinding驱动的`
Ticker 的结构如下:
start 方法:开始 Ticker 时钟,正常情况下,此后每一帧都会调用 Ticker 的回调
从代码看,我们知道 start 方法完成了两件事:
代码1: 执行 ticker 任务—— scheduleTick,后面我们详细看是怎么执行任务的
代码2:** 记录任务开始的时间**
这里注意一下, SchedulerBinding 调度是分阶段的,分为:
idle:index 是 0,没有帧被执行,这个阶段主要执行一些类似微任务的任务
transientCallbacks:index是1,这个阶段处理动画的状态
midFrameMicrotasks:index是2,处理transientCallbacks阶段触发的Microtasks
persistentCallbacks:index是3,处理处理build/layout/paint
postFrameCallbacks:index是4,主要在下一帧之前,做一些清理工作或者准备工作的
下面我们看 ticker 具体的调度方法 scheduleTick
scheduleTick 方法内部很简单,调用了 scheduleFrameCallback 方法。 这个方法就是告诉engine开始调度帧任务吧,并且transientCallbacks阶段需要执行 我给你的_tick回调
那具体的 _tick 是什么呢?
第一:计算时间。当前时间减去开始时间,这样知道动画制定了多长时间了,时间的百分比就知道了
第二:执行构造方法传进来的任务,任务是什么呢?这里留一个悬念
至此,我们捋一下Ticker的流程。
上面 Animation、Simulation 和 Ticker,基本就把动画的概念定义清楚了。
动画串起来
AnimationController 是封装给开发者直接使用的类。提供了开启、停止等方法。下面我先看这个类是什么,然后在看这个类怎么用。
A controller for an animation.
AnimationController是动画控制器并提供了以下的可直接调用的api:
* Play an animation [forward] or in [reverse], or [stop] an animation.
执行动画:开始,反转,暂停等行为
* Set the animation to a specific [value].
设置动画到制定的值
* Define the [upperBound] and [lowerBound] values of an animation.
定义动画的上下边界
* Create a [fling] animation effect using a physics simulation.
使用simulation 创造一个 【fling(阻尼)】动画效果
By default, an [AnimationController] linearly produces values that range
from 0.0 to 1.0, during a given duration. The animation controller generates
a new value whenever the device running your app is ready to display a new
frame (typically, this rate is around 60 values per second).
默认来说,在给定的时间范围内,AnimationController会线性的生成一系列的0到1的值。
在每一帧,AnimationController都会生成一个新的值。
帧率就是 每秒60
AnimationController和Ticker是绑定的,因为上面我们看到了,Ticker可以将调度器绑定起来。
An [AnimationController] needs a [TickerProvider], which is configured using
the `vsync` argument on the constructor.
AnimationController的构造方法中需要一个TickerProvider,并将TickerProvider赋值给vsync参数。
The TickerProvider interface describes a factory for [Ticker] objects. A
[Ticker] is an object that knows how to register itself with the
[SchedulerBinding] and fires a callback every frame. The
[AnimationController] class uses a [Ticker] to step through the animation
that it controls.
TickerProvider接口是工厂模式,负责生产Ticker对象。Ticker对象会在SchedulerBinding中注册,并在每一帧执行自己的回调。
那AnimationController就会用Ticker对象,来执行自己控制的动画
If an [AnimationController] is being created from a [State], then the State
can use the [TickerProviderStateMixin] and [SingleTickerProviderStateMixin]
classes to implement the [TickerProvider] interface. The
[TickerProviderStateMixin] class always works for this purpose; the
[SingleTickerProviderStateMixin] is slightly more efficient in the case of
the class only ever needing one [Ticker] (e.g. if the class creates only a
single [AnimationController] during its entire lifetime).
如果AnimationController在state对象中创建,State可以使用TickerProviderStateMixin和
SingleTickerProviderStateMixin混入类。
如果仅仅是需要一个Ticker,那么可以使用SingleTickerProviderStateMixin。
An [AnimationController] should be [dispose]d when it is no longer needed.
This reduces the likelihood of leaks. When used with a [StatefulWidget], it
is common for an [AnimationController] to be created in the
[State.initState] method and then disposed in the [State.dispose] method.
不在需要动画的时候,需要调用AnimationController的dispose方法,这样可以减少内存泄漏的可能性。
如果在StatefulWidget中使用AnimationController的话,需要在initState中创建,在dispose中dispose。
从上面的描述我们可以知道:
第一:AnimationController 提供了控制动画的能力
第二:AnimationController 构造需要一个 TickerProvider,一般是 State 的混入类 SingleTickerProviderStateMixin 提供。
从前面的文章 👉看完之后,你就能告诉别人哪个类实现了Flutter动画,我们知道了 AnimationController的使用步骤:
首先,给State类上混入SingleTickerProviderStateMixin,在initState中构造,赋值需要的参数,其中 vsync 参数是必须的
在 AnimationController 构造方法中,我们发现动画时长、上下限都是可选的,唯独 vsync
是必须的。那我们就看看为啥它是必须的。
构造方法中,首先执行了 创造ticker 的操作,这个操作就是我们介绍 Ticker
的时候,调用Ticker
的构造方法。
还记得上面的悬念吗?每一滴答执行的任务是啥?就是这个——_ticker!!!!!
所以每一帧都会调用 AnimationController
的 _ticker
方法。就是上面圈起来的地方。
_ticker 干的事就很简单了
void _tick(Duration elapsed) {
_lastElapsedDuration = elapsed;
final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
_value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
...
notifyListeners();
_checkStatusChanged();
}
调用 模拟器的 x 方法,获取当前时间下的具体的动画数值是什么,然后发出通知。
通知的监听者是谁呢?
就是我们经常提到的 addListener
AnimationController controller = AnimationController(vsync: this)
..addListener(() {
//监听者
});
这就是为什么构造 AnimationController 的时候为啥必须一个 vsync 参数,通过这个参数 将帧的调度关联了起来。
驱动动画
前面提到了,构造 AnimationController 将帧的调度关联了起来,保证在每一帧可以让动画动起来。
我们知道,这只是增加了能够动画的能力,还并没有驱动动画。驱动动画是什么呢?就是AnimationController 的 forward 、reverse , fling 等方法
我们以 forward 为例,看驱动的流程。
调用 forward 之后,将动画的运动的方向标记为向前(动画还有向后 reverse),然后动画到指定的动画上界。
做了三件事:首先 :确定动画的时间周期,比如动画300毫秒
然后:生成动画模拟器 _InterpolationSimulation
最后:开启动画
这就是驱动的完整流程。
至此,AnimationController 将动画概念完整的串起来了。
总结
动画就是时间与值的对应关系,Simulation 实现了计算,SchedulerBinding 实现了每一帧动起来的效果,相比 setState 等标记元素为 “脏”, 动画是在绘制之前就完成了计算。而这一切的大管家就是我们常用的 AnimationController 。 愉快的开发动画吧~~~~~。
Flutter 官方动画教程的中文版