动画概览
Flutter 中的动画系统基于 Animation
。Widgets 可以直接将这些动画合并到自己的 build 方法中来读取它们的当前值或者监听它们的状态变化,或者可以将其作为的更复杂动画的基础传递给其他 widgets。
Animation
动画系统的首要组成部分就是 Animation
类。一个动画表现为可在它的生命周期内发生变化的特定类型的值。大多数需要执行动画的 widgets 都需要接收一个 Animation
对象作为参数,从而能从中获取到动画的当前状态值以及应该监听哪些具体值的更改。
addListener
每当动画的状态值发生变化时,动画都会通知所有通过 addListener
添加的监听器。通常,一个正在监听动画的 State
对象会调用自身的 setState
方法,将自身传入这些监听器的回调函数来通知 widget 系统需要根据新状态值进行重新构建。
这种模式非常常见,所以有两个 widgets 可以帮助其他 widgets 在动画改变值时进行重新构建:AnimatedWidget
和 AnimatedBuilder
。第一个是 AnimatedWidget
,对于无状态动画 widgets 来说是尤其有用的。要使用 AnimatedWidget
,只需继承它并实现一个 build
方法。第二个是 AnimatedBuilder
,对于希望将动画作为复杂 widgets 的 build 方法的其中一部分的情况非常有用。要使用 AnimatedBuilder
,只需构造 widget 并将 AnimatedBuilder
传递给 widget 的 builder
方法。
addStatusListener
动画还提供了一个 AnimationStatus
,表示动画将如何随时间进行变化。每当动画的状态发生变化时,动画都会通知所有通过 addStatusListener
添加的监听器。通常情况下,动画会从 dismissed
状态开始,表示它处于变化区间的开始点。举例来说,从 0.0 到 1.0 的动画在 dismissed
状态时的值应该是 0.0。动画进行的下一状态可能是 forward
(比如从 0.0 到 1.0)或者 reverse
(比如从 1.0 到 0.0)。最终,如果动画到达其区间的结束点(比如 1.0),则动画会变成 completed
状态。
AnimationController
要创建动画,首先要创建一个 AnimationController
。除了作为动画本身,AnimationController
还可以用来控制动画。例如,你可以通过控制器让动画正向播放 forward
或停止动画 stop
。你还可以添加物理模拟效果 fling
(例如弹簧效果)来驱动动画。
一旦创建了一个动画控制器,你可以基于它来构建其他动画。例如,你可以创建一个 ReverseAnimation
,效果是复制一个动画但是将其反向运行(比如从 1.0 到 0.0)。同样,你可以创建一个 CurvedAnimation
,效果是用 curve 来调整动画的值。
补间动画
如果想要在 0.0 到 1.0 的区间之外设置动画,可以使用 Tween<T>
,它可以在它的 begin
值和 end
值之间进行插值补间。许多类都有特定的 Tween
子类,它们能提供基于特定类型的插值行为。例如, ColorTween
可以在颜色间进行插值,RectTween
可以在矩形之间进行插值。你可以通过创建自己的 Tween
子类并覆盖其 lerp
方法来定义自己的补间动画。
补间动画本身只定义了如何在两个值之间进行插值。要获取动画当前帧的具体值,还需要一个动画来确定当前状态。有两种方法可以将补间动画与动画组合在一起以获得动画的具体值:
你可以用
evaluate
方法处理动画的当前值从而得到对应的插值。这种方法对于已经监听动画并因此在动画改变值时重新构建的 widgets 是最有效的。你可以用
animate
方法处理一个动画。相对于返回单个值,animate 方法返回一个包含补间动画插值的新的Animation
。这种方法对于当你想要将新创建的动画提供给另一个 widget 时最有效,它可以直接读取包含补间动画的插值以及监听对应插值的更改。
架构
动画实际上是由许多核心模块共同构建的。
调度器
SchedulerBinding
是一个暴露出 Flutter 调度原语的单例类。
在这一节,关键原语是帧回调。每当一帧需要在屏幕上显示时,Flutter 的引擎会触发一个 “开始帧” 回调,调度程序会将其多路传输给所有使用 scheduleFrameCallback()
注册的监听器。所有这些回调不管在任意状态或任意时刻都可以收到这一帧的绝对时间戳。由于所有回调收到时间戳都相同,因此这些回调触发的任何动画看起来都是完全同步的,即使它们需要几毫秒才能执行。
运行器
Ticker
类挂载在调度器的 scheduleFrameCallback()
的机制上,来达到每次运行都会触发回调的效果。
一个 Ticker
可以被启动和停止. 启动时,它会返回一个 Future
,这个 Future
在 Ticker
停止时会被改为完成状态。
每次运行, Ticker
都会为回调函数提供从 Ticker
开始运行到现在的持续时间。
因为运行器总是会提供在自它们开始运行以来的持续时间,所以所有运行器都是同步的。如果你在两帧之间的不同时刻启动三个运行器,它们都会被同步到相同的开始时间,并随后同步运行。
模拟器
Simulation
抽象类将相对时间值(运行时间)映射为双精度值,并且有完成的概念。
原则上,模拟器是无状态的,但在实践中,一些模拟器(例如 BouncingScrollSimulation
和 ClampingScrollSimulation
)在查询时会不可逆地被改变状态。
针对不同的效果,Simulation
类有 各种具体实现。
Animatables
Animatable
抽象类将双精度值映射为特定类型的值。
Animatable
类是无状态和不可变的。
补间动画
Tween
抽象类将名义范围为 0.0-1.0 的双精度值映射到某个类型值(例如 Color
或其他双精度值)。它属于 Animatable
。
它有一个输出类型(T
)的概念,这个输出类型有一个 begin
值和一个end
值,以及在给定输入值的起始值和结束值(名义范围为 0.0-1.0 的双精度值)之间插值(lerp
)的方法。
Tween
类是无状态和不可变的。
组合 animatables
将 Animatable<double>
(父类)传递给一个 Animatable
的 chain()
方法会创建一个新的 Animatable
子类,这个子类会先应用父类的映射,然后应用子类的映射。
曲线
Curve
抽象类将名义范围为 0.0-1.0 的双精度值映射到名义范围为 0.0-1.0 的双精度值。
Curve
类是无状态和不可变的。
动画
Animation
抽象类提供给定类型的值、动画方向的概念和动画状态和一个监听器接口,这个监听器接口用来注册值或状态的改变时被调用的回调。
有些 Animation
的子类值是永远不变的(kAlwaysCompleteAnimation
,kAlwaysDismissedAnimation
,AlwaysStoppedAnimation
),在这些子类上注册回调没有任何效果,因为这些回调永远不会被调用。
Animation<double>
变量很特殊,因为它可以被用来表示名义范围为 0.0-1.0 的双精度值,也就是 Curve
和 Tween
类以及动画的一些其他子类所期望的输入。
有些 Animation
的子类是无状态的,只是将监听器转发给其父级;另外有些是有状态的。
组合动画
大多数 Animation
子类都采用明确的 “父级提供的” Animation<double>
。可以说它们是由父级驱动的。
CurvedAnimation
子类接收一个 Animation<double>
类(父级)和几个 Curve
类(正向和反向曲线)作为输入,并使用父级的值作为输入提供给曲线来确定它的输出。CurvedAnimation
是不可变和无状态的。
ReverseAnimation
子类接收一个 Animation<double>
类作为它的父级,但反转动画所有的值。它假定父级使用名义范围为 0.0-1.0 的双精度值,并返回范围为 1.0-0.0 的值。父级动画的状态和方向也会被反转。ReverseAnimation
是不可变和无状态的。
ProxyAnimation
子类接收一个 Animation<double>
类作为其父级,并仅转发该父级的当前状态。然而,父级是可变的。
TrainHoppingAnimation
子类接收两个父类,并在它们的值交叉时在它们之间切换。
动画控制器
AnimationController
是一个有状态的 Animation<double>
,并使用一个 Ticker
来提供生命周期,它可以被启动和停止。每次运行,它会收集从启动开始经过的时间,并将其传递给 Simulation
来获得一个值,这就是在当前时间戳下它应该传递的值。如果 Simulation
反馈此时动画已经结束了,则控制器就会自行停止。
可以给动画控制器设置动画运行的下限和上限,还有动画的持续时间。
在一般情况下(使用 forward()
、reverse()
、play()
或者 resume()
),动画控制器只是简单地在持续时间内线性地从下限至上限(反之亦然,用于在反向方向)进行插值补间。
当使用 repeat()
时,动画控制器会在持续时间内线性地在上下边界之间进行插值补间,但会一直重复,不会停止。
当使用 animateTo()
时,动画控制器会在持续时间内线性地从当前值到给定目标值进行插值补间。如果方法没有指定持续时间,则使用控制器的默认持续时间和控制器的上下限范围来确定动画的速度。
当使用 fling()
时,一个 Force
被用来创建一个特定的模拟器,然后用来驱动控制器。
当使用 animateWith()
时,给定的模拟器会被用于驱动控制器。
这些方法都会返回 Ticker
提供的将来值,交由控制器下一次停止或改变模拟器时来完成。
将 animatables 附加到动画上
将 Animation<double>
(新父级)传递给一个 Animatable
类的 animate()
方法将创建一个新的 Animation
子类,它的作用类似于 Animatable
,但是由给定的父级驱动。