动画 API 概览
Flutter 中的动画系统基于类型化的 Animation
对象。Widget 可以通过直接读取其当前值并监听其状态变化,将其动画直接整合到其构建函数中,或者它们可以使用这些动画作为基础,构建更精密的动画并将其传递给其他 widget。
动画
#动画系统的主要构建块是 Animation
类。动画表示一个特定类型的值,该值可以在动画的生命周期内发生变化。大多数执行动画的 widget 都将一个 Animation
对象作为参数接收,它们从该对象读取动画的当前值,并监听该值的变化。
addListener
#每当动画的值发生变化时,动画都会通知所有通过 addListener
添加的监听器。通常,监听动画的 State
对象会在其监听器回调中调用其自身的 setState
,以通知 widget 系统它需要使用动画的新值进行重建。
这种模式非常常见,因此有两个 widget 可以帮助 widget 在动画值发生变化时重建:AnimatedWidget
和 AnimatedBuilder
。第一个 AnimatedWidget
最适用于无状态的动画 widget。要使用 AnimatedWidget
,只需对其进行子类化并实现 build
函数。第二个 AnimatedBuilder
适用于希望将动画作为更大构建函数的一部分的更复杂 widget。要使用 AnimatedBuilder
,只需构造 widget 并向其传递一个 builder
函数。
addStatusListener
#动画还提供 AnimationStatus
,它指示动画将如何随时间演变。每当动画的状态发生变化时,动画都会通知所有通过 addStatusListener
添加的监听器。通常,动画最初处于 dismissed
状态,这意味着它们处于其范围的开始。例如,从 0.0 到 1.0 的动画在其值为 0.0 时将是 dismissed
。动画随后可能会 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
调整。
Tweens
#要将动画超出 0.0 到 1.0 的范围,你可以使用 Tween<T>
,它在其 begin
和 ColorTween
在颜色之间进行插值,而 RectTween
在矩形之间进行插值。你可以通过创建自己的 Tween
子类并覆盖其 lerp
函数来定义自己的插值。
就其本身而言,补间只是定义如何在两个值之间进行插值。要获取动画当前帧的具体值,你还需要一个动画来确定当前状态。有两种方法可以将补间与动画结合起来以获取具体值
你可以在动画的当前值处
evaluate
补间。这种方法对于已经监听动画并在动画值发生变化时进行重建的 widget 最有用。你可以根据动画
animate
补间。animate 函数不是返回单个值,而是返回一个包含补间的新Animation
。当你想将新创建的动画提供给另一个 widget 时,这种方法最有用,该 widget 可以读取包含补间的当前值以及监听值的变化。
架构
#动画实际上是由许多核心构建块构建的。
调度器
#SchedulerBinding
是一个单例类,它暴露了 Flutter 的调度原语。
对于本讨论,关键原语是帧回调。每当需要在屏幕上显示帧时,Flutter 的引擎会触发一个“开始帧”回调,调度器将其多路复用到所有使用 scheduleFrameCallback()
注册的监听器。所有这些回调都以 Duration
形式的时间戳,从某个任意纪元开始,接收帧的官方时间戳。由于所有回调具有相同的时间,因此从这些回调触发的任何动画都将显得完全同步,即使它们需要几毫秒才能执行。
Tickers
#Ticker
类通过调度器的 scheduleFrameCallback()
机制在每个 tick 调用回调。
Ticker
可以启动和停止。启动时,它返回一个 Future
,该 Future
将在它停止时解析。
在每个 tick,Ticker
会向回调提供自启动后第一个 tick 以来的持续时间。
由于 tickers 总是提供相对于它们启动后第一个 tick 的经过时间;tickers 都是同步的。如果你在两个 tick 之间以不同的时间启动三个 tickers,它们仍然会同步到相同的启动时间,并随后同步地计时。就像公交车站的人们一样,所有 tickers 都等待定期发生的事件(tick)开始移动(计时)。
模拟
#Simulation
抽象类将相对时间值(经过时间)映射到双精度值,并具有完成的概念。
原则上,模拟是无状态的,但在实践中,某些模拟(例如,BouncingScrollSimulation
和 ClampingScrollSimulation
)在查询时会不可逆地改变状态。
有 各种具体的 Simulation
类实现,用于不同的效果。
Animatables
#Animatable
抽象类将双精度值映射到特定类型的值。
Animatable
类是无状态和不可变的。
Tweens
#Tween<T>
抽象类将名义上在 0.0-1.0 范围内的双精度值映射到类型化的值(例如,Color
或另一个双精度值)。它是一个 Animatable
。
它具有输出类型 (T
) 的概念、该类型的 begin
值和 end
值,以及在给定输入值(名义上在 0.0-1.0 范围内的双精度值)下在 begin 和 end 值之间进行插值 (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
子类都采用显式的“父级” 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
来赋予自己生命。它可以启动和停止。在每个 tick 时,它获取自启动以来的经过时间,并将其传递给 Simulation
以获得一个值。然后,它报告该值。如果 Simulation
报告在该时间它已结束,则控制器会停止自身。
动画控制器可以指定动画的下限和上限,以及持续时间。
在简单情况下(使用 forward()
或 reverse()
),动画控制器仅在给定持续时间内从下限到上限(或反之,对于反向)进行线性插值。
使用 repeat()
时,动画控制器在给定持续时间内使用给定边界之间的线性插值,但不会停止。
使用 animateTo()
时,动画控制器在给定持续时间内从当前值到给定目标进行线性插值。如果未向方法提供持续时间,则使用控制器的默认持续时间以及控制器下限和上限描述的范围来确定动画的速度。
使用 fling()
时,使用 Force
创建一个特定的模拟,然后使用该模拟来驱动控制器。
使用 animateWith()
时,使用给定的模拟来驱动控制器。
这些方法都返回 Ticker
提供的未来,该未来将在控制器下次停止或更改模拟时解析。
将 animatables 附加到动画
#将 Animation<double>
(新的父级)传递给 Animatable
的 animate()
方法会创建一个新的 Animation
子类,该子类行为类似于 Animatable
,但由给定的父级驱动。