本文翻译自👉Animations overview
,介绍了Flutter 动画相关的类,这些类的相互配合才构成了整个 Flutter 的动画大厦。
Flutter 的动画体系是建立在 Animation
类之上的。Widget 可以读取动画当前的值,也可以监听状态的改变,然后将这些信息直接合并应用自己的build方法中,Widget 就动起来了。Widget 也能将自己的动画传递给其他 Widget ,其他 Widget 使用传递的动画作为更高级动画的基础。
Animation
Animation
类很重要,是动画框架实现的主力。动画就是一个特定类型的值,这个值可以在动画的生命周期内改变,简单来说,动画就是时间与值的对应关系。大多数动画 Widget,都接受一个 Animation
类型的对象作为其参数,Widget 就从这个对象中读取当前动画的值,然后响应这个值的变化。
addListener
只要动画的值发生了改变,动画就会通知所有的监听者。调用动画的addListener
方法,就可以成为动画的监听者。一般来说,[State
]对象会在动画发生改变的时候,在监听的方法体中,调用 [setState
] 方法,Widget 就会使用新的动画值来重新构建。伪代码如下:
Animation<T> animation;
@override
void initState() {
super.initState();
animation.addListener((){
setState(() {
});
});
}
这种模式非常常见。当动画的值发生改变时,有两个 Widget 可以帮助其他 Widget 重新构建:AnimatedWidget
和 AnimatedBuilder
。
AnimatedWidget
的使用场景主要是 为 StatelessWidget 添加动画效果,只需要继承自 AnimatedWidget
,重写 build
方法就可以了。AnimatedBuilder
的使用场景主要是希望超长的build构建中某一部分具有动画效果。需要在构建 AnimatedBuilder
的时候传入一个 builder
方法。
addStatusListener
动画也提供了状态字段——AnimationStatus
,来表示下一个时间段动画该怎么样,比如停止、继续、反转等等。只要动画的状态改变了,动画就会通知所有的状态监听者,可以调用动画的 addStatusListener
成为其监听者。一般动画是从 dismissed
开始执行的, dismissed
状态是动画的时间范围或者值的范围的开始。
比如,如果动画的范围是从 0.0 to 1.0,那么动画的值是 0.0 的时候,状态会变成 dismissed
。动画一般是前进的-——forward
(从 0.0 到 1.0),但是也可以是反向的—— reverse
(从 1.0 到 0.0)。最终,如果动画达到了范围的末端,动画就达到了 completed
状态。
AnimationController
在创建动画之前,需要先创建一个AnimationController
,AnimationController
有两个作用,第一,它继承自 Animation
,本身就是动画,把它当作动画来传参。第二,它可以控制动画,比如动画的前进、停止。除了基本的动画用法之外,AnimationController
还可以使用物理模拟器来fling
动画,让动画有物理仿真效果。比如 弹跳等等。
因为 AnimationController
本身就是 Animation
的子类,而动画又是可以嵌套的,所以就可以在 AnimationController
的基础上构建其他的动画。比如,可以构建 ReverseAnimation
动画,实现镜像效果,可以构建特定曲线的 CurvedAnimation
动画。
Tweens
有时动画不是简单的0.0至1.0区间的值,那么开发者就可以使用Tween<T>
了。Tween<T>
可以在begin
和 end
之间的过程值基础上进行计算,比如计算出颜色,计算出字符串等等,计算出的结果是补间值,或者效果值。补间值可以是指定类型的。比如 ColorTween
就是颜色的值。RectTween
就是矩形的值。开发者也可以自定义补间值,只需要继承自 Tween
,重写 lerp
方法。
Tween<T>
只负责根据动画过程0-1产生的中间值,来生成真正的效果值。为了得到当前帧的这个中间值,开发者就需要想办法得到这个值,addListener
可以得到这个中间值。有两种方法可以将Tween<T>
与动画结合起来以获得一个具体的补间值:
-
直接根据当前的动画值计算补间值。这种方法适合已经监听了动画的 Widget(
addListener
),在监听中来重新构建。 -
在动画的基础上对补间值进行二次
animate
动画。animate
方法返回一个新的补间动画,而不是返回单个值。这种方法适用于给其他 Widget(A) 创建一个新的动画,这个 A Widget 读取和监听当前的补间值。就是 A 不关心原始的值是什么,只关心原始对应的补间值是什么。比如 A 只关心颜色是红色的时候,它显示绿色,它在红色的基础上进行计算。
动画架构
动画实是建立在一些核心构建块基础之上的。
Scheduler
[SchedulerBinding
]是一个单例类,暴漏了最原始的Flutter调度过程。
对于动画来说,最关键的是帧调度。每一帧都需要展示的屏幕上,Flutter’s 引擎会触发 begin frame
回调,调度器会把这个回调发送给每一个回调监听者。可以调用scheduleFrameCallback()
方法注册成为监听者。
所有注册的监听者,都会收到 Duration
形式的帧的时间戳。因为注册者的获得的时间戳是相同的,所以它们触发的动画看起来也是同步的,差距的话,也可能是仅仅几毫秒。
Tickers
Ticker
衔接了调度器的 scheduleFrameCallback()
,会在每一个 tick 调用自己的回调。
Ticker
可以开始也可以停止。开始的时候会产生一个 Future
,结束的时候会计算出 Future
的结果。
每一次 tick,Ticker
都会调用一次回调,并且会调的入参是第一次 tick 到现在持续的时长。
因为 ticker 传给回调的时间戳是相对于第一个 tick 的时长,所以所有的tickers都是同步的。 如果我们在两个 tick 之间开启了第三个,它们的回调的参数的时间也是一样的。就像公交车站, 所有的 ticker 都在等待一个定期发生的事件(tick),去开始移动(计时)。
Simulations
Simulation
是一个抽象类,将一个时间值转为一个double值,并且有完成的概念。
原则上,Simulation
是无状态的,但是在实操中,一些 simulation 会在被查询的时候不可逆的改变状态,比如 BouncingScrollSimulation
和 ClampingScrollSimulation
。
various concrete implementations 中举了一些例子来展示不同的效果。
Animatables
Animatable
是抽象类,将一个 double 映射为一个特定的类型。并且 Animatable
是无状态的不可变的。
Tweens
Tween<T>
将 double 映射为一个特定的类型,比如颜色等等,它是 Animatable
,在 lerp
方法中,完成 double 到特定类型的转换。
Tween
是无状态的不可变的。
Composing animatables
将 Animatable<double> A
传递给 Animatable<double> B
的 chain()
方法,这样会创建一个新的 Animatable C
,那么 C 的效果就是:原始的 double 先映射成 A, 然后 A 再映射成 B。
Curves
Curve
是抽象类,作用是将一个 0.0-1.0 范围内的 double 值,转为一个 0.0-1.0 范围内的 double 值。也是无状态的不可变的。它的作用就是定义动画的曲线。
Animations
Animation
是抽象类,定义了一些动画的协议接口:获取指定类型动画的值,动画的方向。动画的状态。动画值变化的监听者。
有一些子类的动画值是不会变化的,比如 kAlwaysCompleteAnimation
, kAlwaysDismissedAnimation
,AlwaysStoppedAnimation
等等。这些动画的监听者也不会被调用。
一些 Animation
子类是无状态的,只是将监听者传给它们的父动画。有些是有状态的。
Composable animations
大多数 Animation
动画都接受一个显式的父动画 Animation<double>
。这些动画是被父动画驱动的的。
CurvedAnimation
的入参是:一个父动画 Animation<double>
、正向的曲线 Curve
,反向的曲线 Curve
。CurvedAnimation
使用曲线来计算父动画的输入,将结果作为自己的动画效果。CurvedAnimation
是不可变的无状态的。
ReverseAnimation
的入参:一个父动画 Animation<double>
,它会将父动画反转。比如父动画0.2对应的值是3,0.8对应的值是6。那么ReverseAnimation
动画0.2对应的值是6,0.8对应的值是3,就像镜子一样。CurvedAnimation
是不可变的无状态的。
ProxyAnimation
的入参:一个父动画 Animation<double>
,仅仅是转发父动画当前的值。父动画是可变的。
TrainHoppingAnimation
动画有两个父动画,当动画交叉的时候,会在它们之间切换。
Animation controllers
AnimationController
是一个有状态的 Animation<double>
,它内部会持有一个 Ticker
,通过 Ticker
有了生命周期:开始和停止。 Ticker
的每一次回调,这个回调我们暂且叫为嘀嗒 ,每嘀嗒一下,AnimationController
都会执行下面的流程:
第一步,嘀嗒的参数中获取时间参数。第二步,将时间参数传给 Simulation
,并从 Simulation
获得动画的效果值。在这个过程中,如果 Simulation
上报时间已经到了,动画到了动画的持续时间,AnimationController
就会停止自己。
AnimationController
AnimationController
需要动画的上下界和动画持续时间。
在一些简单的例子中,比如 forward()
或者 reverse()
。AnimationController
在给定的时间内,在上下届范围内插一系列的值。
如果使用 repeat()
,AnimationController
也会插入一系列的值,只是不会停止,会重复。
如果使用 animateTo()
,AnimationController
会在给定的时间范围内,从当前的动画值到给定的目标值之间,插一系列的值。如果没有给定时间范围,就是 duration 是 null,那么AnimationController
会用默认的动画时间和动画默认的上下届来确定动画的速度。
如果使用 fling()
,会用 Force
创建动画的模拟器 Simulation
,使用模拟器来驱动 AnimationController
。
如果使用 animateWith()
, 那么会用给定的模拟器 Simulation
来驱动 AnimationController
。
上面介绍的方法都会返回一个 future,这个 future 就是 Ticker
提供的。当AnimationController
停止或者改变模拟器的时候, future 就会返回结果。
总结