Flutter 中的动画系统基于类型化的 Animation 对象。Widget 可以通过直接读取其当前值并监听其状态变化,将其动画直接整合到其构建函数中,或者它们可以使用这些动画作为基础,构建更精密的动画并将其传递给其他 widget。

动画

#

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

addListener

#

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

这种模式非常常见,因此有两个 widget 可以帮助 widget 在动画值发生变化时重建:AnimatedWidgetAnimatedBuilder。第一个 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 状态。

Animation­Controller

#

要创建动画,首先创建 AnimationController。除了自身是一个动画之外,AnimationController 还允许你控制动画。例如,你可以告诉控制器 forward 播放动画或 stop 动画。你还可以 fling 动画,它使用物理模拟(例如弹簧)来驱动动画。

创建动画控制器后,你就可以开始构建基于它的其他动画。例如,你可以创建一个 ReverseAnimation,它镜像原始动画但以相反方向运行(从 1.0 到 0.0)。同样,你可以创建一个 CurvedAnimation,其值由 Curve 调整。

Tweens

#

要将动画超出 0.0 到 1.0 的范围,你可以使用 Tween<T>,它在其 beginColorTween 在颜色之间进行插值,而 RectTween 在矩形之间进行插值。你可以通过创建自己的 Tween 子类并覆盖其 lerp 函数来定义自己的插值。

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

  1. 你可以在动画的当前值处 evaluate 补间。这种方法对于已经监听动画并在动画值发生变化时进行重建的 widget 最有用。

  2. 你可以根据动画 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 抽象类将相对时间值(经过时间)映射到双精度值,并具有完成的概念。

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

各种具体的 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>(父级)传递给 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 来赋予自己生命。它可以启动和停止。在每个 tick 时,它获取自启动以来的经过时间,并将其传递给 Simulation 以获得一个值。然后,它报告该值。如果 Simulation 报告在该时间它已结束,则控制器会停止自身。

动画控制器可以指定动画的下限和上限,以及持续时间。

在简单情况下(使用 forward()reverse()),动画控制器仅在给定持续时间内从下限到上限(或反之,对于反向)进行线性插值。

使用 repeat() 时,动画控制器在给定持续时间内使用给定边界之间的线性插值,但不会停止。

使用 animateTo() 时,动画控制器在给定持续时间内从当前值到给定目标进行线性插值。如果未向方法提供持续时间,则使用控制器的默认持续时间以及控制器下限和上限描述的范围来确定动画的速度。

使用 fling() 时,使用 Force 创建一个特定的模拟,然后使用该模拟来驱动控制器。

使用 animateWith() 时,使用给定的模拟来驱动控制器。

这些方法都返回 Ticker 提供的未来,该未来将在控制器下次停止或更改模拟时解析。

将 animatables 附加到动画

#

Animation<double>(新的父级)传递给 Animatableanimate() 方法会创建一个新的 Animation 子类,该子类行为类似于 Animatable,但由给定的父级驱动。