跳至主要内容

动画 API 概览

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 状态。

Animation­Controller

#

要创建动画,首先创建一个 AnimationControllerAnimationController 本身也是一个动画,它允许你控制动画。例如,你可以告诉控制器播放动画 forwardstop 动画。你还可以 fling 动画,它使用物理模拟(如弹簧)来驱动动画。

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

补间动画

#

要超出 0.0 到 1.0 的区间进行动画,可以使用 Tween<T>,它在其 beginend 值之间进行插值。许多类型都有特定的 Tween 子类,提供类型特定的插值。例如,ColorTween 在颜色之间进行插值,而 RectTween 在矩形之间进行插值。你可以通过创建自己的 Tween 子类并覆盖其 lerp 函数来定义自己的插值。

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

  1. 你可以在动画的当前值处 evaluate 缓动。这种方法最适合已经监听动画的小部件,因此每当动画值发生变化时都会重新构建。

  2. 你可以根据动画 animate 缓动。animate 函数返回一个新的 Animation,而不是返回单个值,该 Animation 集成了缓动。这种方法最适合当你想要将新创建的动画提供给另一个小部件时,该小部件随后可以读取包含缓动的当前值以及监听值的变化。

架构

#

动画实际上是由许多核心构建块构建的。

调度程序

#

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

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

滴答器

#

Ticker 类挂接到调度程序的 scheduleFrameCallback() 机制,以便在每次滴答时调用回调。

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

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

因为滴答器总是相对于启动后第一次滴答提供其经过时间;所以所有滴答器都是同步的。如果在两次滴答之间以不同的时间启动三个滴答器,那么它们仍然会与相同的起始时间同步,并且随后会步调一致地滴答。就像公交车站的人一样,所有滴答器都在等待定期发生的事件(滴答)开始移动(计时)。

模拟

#

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

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

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

可动画对象

#

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

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

补间动画

#

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

它具有输出类型(T)、该类型的 begin 值和 end 值以及一种针对给定输入值(名义上在 0.0-1.0 范围内的双精度值)在 begin 和 end 值之间进行插值(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,但由给定的父级驱动。