动画 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
本身就是一个动画,它允许你控制动画。例如,你可以告诉控制器播放动画 forward
或 stop
动画。你还可以 fling
动画,它使用物理模拟(例如弹簧)来驱动动画。
创建动画控制器后,你可以开始基于它构建其他动画。例如,你可以创建一个 ReverseAnimation
,它镜像原始动画,但方向相反(从 1.0 到 0.0)。类似地,你可以创建一个 CurvedAnimation
,其值由 Curve
调整。
插值器
#要对超过 0.0 到 1.0 区间的动画进行动画处理,可以使用 Tween<T>
,它在其 begin
和 end
值之间进行插值。许多类型都有特定的 Tween
子类,它们提供类型特定的插值。例如,ColorTween
在颜色之间进行插值,而 RectTween
在矩形之间进行插值。你可以通过创建 Tween
的子类并覆盖其 lerp
函数来定义你自己的插值。
就其本身而言,插值器只是定义了如何在两个值之间进行插值。要获得动画当前帧的具体值,你还需要一个动画来确定当前状态。有两种方法可以将插值器与动画组合以获得具体值:
你可以在动画的当前值处
evaluate
插值器。这种方法最适用于已经监听动画的 Widget,因此每当动画的值发生变化时都会重新构建。你可以基于动画
animate
插值器。animate
函数不是返回单个值,而是返回一个包含插值器的新Animation
。这种方法最适用于你想要将新创建的动画提供给另一个 Widget 的情况,然后该 Widget 可以读取包含插值器的当前值以及监听值的更改。
架构
#动画实际上是由许多核心构建块构建的。
调度器
#SchedulerBinding
是一个单例类,它公开 Flutter 调度原语。
对于此讨论,关键原语是帧回调。每次需要在屏幕上显示帧时,Flutter 引擎都会触发一个“begin frame”回调,调度程序使用 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 范围内的双精度值)的情况下在 begin 值和 end 值之间进行插值 (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
,但由给定的父级驱动。
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。