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