Flutter 中的动画系统基于类型化的 Animation 对象。小部件可以直接在其构建函数中通过读取当前值和监听状态变化来集成这些动画,或者它们可以将这些动画作为更复杂动画的基础,并将它们传递给其他小部件。

动画

#

动画系统的主要构建块是 Animation 类。动画表示在动画生命周期内可以更改的特定类型的值。大多数执行动画的小部件都接收一个 Animation 对象作为参数,它们从中读取动画的当前值并监听该值的变化。

addListener

#

每当动画的值发生变化时,动画会通知所有通过 addListener 添加的监听器。通常,监听动画的 State 对象会在其监听器回调中调用自身的 setState,以通知小部件系统需要使用动画的新值进行重建。

这种模式非常常见,因此有两种小部件可以帮助小部件在动画值变化时进行重建:AnimatedWidgetAnimatedBuilder。第一个 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>,它在其 beginend 值之间进行插值。许多类型都有特定的 Tween 子类,提供类型特定的插值。例如,ColorTween 在颜色之间进行插值,RectTween 在矩形之间进行插值。你可以通过创建自己的 Tween 子类并覆盖其 lerp 函数来定义自己的插值。

就其本身而言,补间动画(tween)仅定义了如何在两个值之间进行插值。要获取动画当前帧的具体值,你还需要一个动画来确定当前状态。有两种方法可以将补间动画与动画结合起来以获得具体值:

  1. 你可以根据动画的当前值 evaluate 补间动画。这种方法对于已经监听动画并因此在动画值变化时重建的小部件最有用。

  2. 你可以根据动画 animate 补间动画。animate 函数不会返回单个值,而是返回一个新的、包含补间动画的 Animation。当你想将新创建的动画传递给另一个小部件时,这种方法最有用,该小部件可以读取包含补间动画的当前值,并监听值的变化。

架构

#

动画实际上是由多个核心构建块组成的。

调度器

#

SchedulerBinding 是一个单例类,它公开了 Flutter 的调度原语。

对于本次讨论,关键原语是帧回调。每当屏幕上需要显示一帧时,Flutter 引擎会触发一个“开始帧”回调,调度器将该回调多路复用到所有通过 scheduleFrameCallback() 注册的监听器。所有这些回调都以一个任意纪元开始的 Duration 形式,获得帧的官方时间戳。由于所有回调都具有相同的时间,因此从这些回调触发的任何动画都将显示为精确同步的,即使它们需要几毫秒才能执行。

计时器

#

Ticker 类通过调度器的 scheduleFrameCallback() 机制,在每次“滴答”(tick)时调用回调。

一个 Ticker 可以被启动和停止。启动时,它返回一个 Future,该 Future 在它停止时解析。

每次“滴答”时,Ticker 会向回调提供自其启动后的第一次“滴答”以来的持续时间。

因为计时器总是提供自其启动后第一次“滴答”以来的持续时间;所以所有计时器都是同步的。如果你在两次“滴答”之间不同时间启动三个计时器,它们都将与相同的起始时间同步,并随后同步“滴答”。就像公交车站的人一样,所有计时器都等待一个定期发生的事件(“滴答”)来开始移动(计时)。

模拟

#

Simulation 抽象类将相对时间值(已逝时间)映射到一个双精度值,并具有完成的概念。

原则上,模拟是无状态的,但实际上,一些模拟(例如,BouncingScrollSimulationClampingScrollSimulation)在被查询时会不可逆地改变状态。

多种 Simulation 类的具体实现,用于不同的效果。

可动画对象

#

Animatable 抽象类将双精度值映射到特定类型的值。

Animatable 类是无状态且不可变的。

补间动画

#

Tween<T> 抽象类将名义范围在 0.0-1.0 的双精度值映射到类型化值(例如,Color 或另一个双精度值)。它是一个 Animatable

它具有输出类型 (T) 的概念,该类型的 begin 值和 end 值,以及在给定输入值(名义范围在 0.0-1.0 的双精度值)下在开始值和结束值之间进行插值 (lerp) 的方法。

Tween 类是无状态且不可变的。

组合可动画对象

#

将一个 Animatable<double>(父级)传递给 Animatablechain() 方法会创建一个新的 Animatable 子类,该子类首先应用父级的映射,然后应用子级的映射。

曲线

#

Curve 抽象类将名义范围在 0.0-1.0 的双精度值映射到名义范围在 0.0-1.0 的双精度值。

Curve 类是无状态且不可变的。

动画

#

Animation 抽象类提供给定类型的值、动画方向和动画状态的概念,以及一个监听器接口,用于注册在值或状态改变时调用的回调。

Animation 的一些子类具有永不改变的值(kAlwaysCompleteAnimationkAlwaysDismissedAnimationAlwaysStoppedAnimation);在这些动画上注册回调没有效果,因为回调永远不会被调用。

Animation<double> 变体很特别,因为它可用于表示名义范围在 0.0-1.0 的双精度值,这是 CurveTween 类以及 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>(新父级)传递给 Animatableanimate() 方法会创建一个新的 Animation 子类,该子类行为类似于 Animatable,但由给定的父级驱动。