跳到主内容

动画简介

如何在 Flutter 中实现动画。

精心设计的动画能让用户界面(UI)更直观,为应用带来流畅且精致的视觉体验,从而提升整体用户体验。Flutter 的动画支持使得实现各种类型的动画变得非常容易。许多组件,特别是 Material 组件,都内置了设计规范中定义的标准运动效果,当然你也可以自定义这些效果。

选择动画实现方式

#

在 Flutter 中创建动画时,有多种途径可选。哪种方式最适合你?为了帮助你做出决定,请观看视频:如何选择适合你的 Flutter 动画组件?(本文亦以配套文章形式发布。)

在 YouTube 新标签页中观看:“如何选择适合你用例的 Flutter 动画组件”

(欲深入了解决策过程,请观看在 Flutter Europe 上发表的视频:Flutter 动画的正确实现方式。)

正如视频所示,以下决策树可以帮助你确定在实现 Flutter 动画时应使用哪种方法

The animation decision tree

动画深度解析

#

如需深入了解 Flutter 动画的工作原理,请观看动画深度解析。(亦有配套文章。)

在 YouTube 新标签页中观看:“深入探索 Flutter 动画”

隐式与显式动画

#

预封装的隐式动画

#

如果预封装的隐式动画(最容易实现的动画)符合你的需求,请观看隐式动画基础。(亦有配套文章。)

在 YouTube 新标签页中观看:“Flutter 隐式动画基础”

自定义隐式动画

#

若要创建自定义隐式动画,请观看使用 TweenAnimationBuilder 创建自定义隐式动画。(亦有配套文章。)

在 YouTube 新标签页中观看:“使用 TweenAnimationBuilder 创建自定义隐式动画”

内置显式动画

#

若要创建显式动画(由你而不是框架来控制动画),你可以考虑使用内置的显式动画类。更多信息,请观看通过内置显式动画制作你的第一个定向动画。(亦有配套文章。)

在 YouTube 新标签页中观看:“通过内置显式动画制作你的第一个定向动画”

显式动画

#

如果需要从零开始构建显式动画,请观看使用 AnimatedBuilder 和 AnimatedWidget 创建自定义显式动画。(亦有配套文章。)

在 YouTube 新标签页中观看:“使用 AnimatedBuilder 和 AnimatedWidget 创建自定义显式动画”

动画类型

#

通常,动画分为补间(Tween)动画和基于物理的动画。以下部分解释了这些术语的含义,并提供了进一步学习的资源。

补间(Tween)动画

#

“补间”是 in-betweening 的缩写。在补间动画中,不仅要定义开始和结束点,还要定义时间线,以及决定过渡时机和速度的曲线。框架会自动计算从起点到终点的过渡方式。

基于物理的动画

#

在基于物理的动画中,运动被模拟为接近现实世界的行为。例如,当你抛出一个球时,球落下的位置和时间取决于抛出的速度以及距离地面的高度。同样,挂在弹簧上的球落下(和弹起)的方式与挂在绳子上的球落下是不同的。

常见动画模式

#

大多数 UX 或动效设计师都会发现某些动画模式在设计 UI 时会被反复使用。本节列出了一些常见的动画模式,并指引你深入了解它们。

动画列表或网格

#

此模式涉及对列表或网格中元素的添加或移除进行动画处理。

  • AnimatedList 示例
    此演示来自 示例应用库,展示了如何为向列表添加元素或移除选定元素添加动画。当用户使用加号 (+) 和减号 (-) 按钮修改列表时,内部的 Dart 列表会同步更新。

共享元素过渡

#

在此模式下,用户从页面中选择一个元素(通常是图片),UI 将选中的元素以动画形式过渡到包含更多详情的新页面。在 Flutter 中,你可以使用 Hero 组件轻松实现路由(页面)之间的共享元素过渡。

  • Hero 动画 如何创建两种风格的 Hero 动画

    • Hero 元素在页面间飞行时同时改变位置和大小。
    • Hero 元素的边界在从一个页面飞往另一个页面时改变形状,例如从圆形变为方形。
  • 另请参阅 HeroNavigatorPageRoute 类的 API 文档。

交错动画

#

将动画分解为较小的运动,其中部分运动存在延迟。这些较小的动画可以是顺序的,也可以是部分或完全重叠的。

动画核心概念与类

#

Flutter 的动画系统基于类型化的 Animation 对象。组件既可以在构建函数(build function)中通过读取其当前值并监听状态变化来直接合并这些动画,也可以将它们作为更复杂动画的基础,再传递给其他组件。

Animation<double>

#

在 Flutter 中,Animation 对象本身并不了解屏幕上的具体内容。Animation 是一个抽象类,它了解其当前值和状态(已完成或已取消)。最常用的动画类型之一是 Animation<double>

Animation 对象会在特定持续时间内顺序生成两个值之间的插值数字。Animation 对象的输出可以是线性的、曲线的、阶梯函数,或任何你可以创建的映射。取决于 Animation 对象的控制方式,它还可以反向运行,甚至在中间改变方向。

动画还可以对除 double 以外的类型进行插值,例如 Animation<Color>Animation<Size>

Animation 对象具有状态。其当前值始终可以通过 .value 成员获取。

Animation 对象本身不涉及渲染或 build() 函数。

CurvedAnimation

#

CurvedAnimation 将动画的进度定义为非线性曲线。

dart
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);

CurvedAnimationAnimationController(将在下一节描述)都是 Animation<double> 类型,因此可以互换使用。CurvedAnimation 会包装它所修改的对象——你不需要通过子类化 AnimationController 来实现曲线效果。

你可以结合 CurvedAnimation 使用 CurvesCurves 类定义了许多常用的曲线,你也可以创建自己的曲线。例如:

dart
import 'dart:math';

class ShakeCurve extends Curve {
  @override
  double transform(double t) => sin(t * pi * 2);
}

如果你想对 Tween 应用动画曲线,请考虑使用 CurveTween

AnimationController

#

AnimationController 是一个特殊的 Animation 对象,每当硬件准备好新帧时,它就会生成一个新值。默认情况下,AnimationController 在给定的时间内线性地产生从 0.0 到 1.0 的数字。例如,以下代码创建了一个 Animation 对象,但不会立即运行:

dart
controller = AnimationController(
  duration: const Duration(seconds: 2),
  vsync: this,
);

AnimationController 派生自 Animation<double>,因此可以在需要 Animation 对象的任何地方使用。然而,AnimationController 具有额外的方法来控制动画。例如,你可以通过 .forward() 方法开始动画。数字的生成与屏幕刷新同步,因此通常每秒生成 60 个数字。在每个数字生成后,每个 Animation 对象都会调用附加的 Listener 对象。若要为每个子组件创建自定义显示列表,请参考 RepaintBoundary

创建 AnimationController 时,需要传入 vsync 参数。vsync 的存在可以防止屏幕外的动画消耗不必要的资源。你可以通过在类定义中添加 SingleTickerProviderStateMixin 来将当前有状态对象作为 vsync 使用。你可以在 GitHub 上的 animate1 示例中查看具体用法。

Tween

#

默认情况下,AnimationController 对象的范围从 0.0 到 1.0。如果需要不同的范围或不同的数据类型,可以使用 Tween 来配置动画,以插值到不同的范围或数据类型。例如,以下 Tween 从 -200.0 到 0.0:

dart
tween = Tween<double>(begin: -200, end: 0);

Tween 是一个无状态对象,仅包含 beginendTween 的唯一职责是定义从输入范围到输出范围的映射。输入范围通常是 0.0 到 1.0,但这不是必需的。

Tween 继承自 Animatable<T>,而不是 Animation<T>。与 Animation 一样,Animatable 的输出不必是 double 类型。例如,ColorTween 指定了两种颜色之间的渐变过程。

dart
colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);

Tween 对象不存储任何状态。相反,它提供了 evaluate(Animation<double> animation) 方法,该方法使用 transform 函数将动画的当前值(介于 0.0 和 1.0 之间)映射为实际的动画值。

Animation 对象的当前值可以在 .value 方法中找到。evaluate 函数还会执行一些清理工作,例如确保在动画值为 0.0 和 1.0 时分别返回 begin 和 end 值。

Tween.animate

#

若要使用 Tween 对象,请在 Tween 上调用 animate(),并将控制器对象传入。例如,以下代码在 500 毫秒内生成从 0 到 255 的整数值:

dart
AnimationController controller = AnimationController(
  duration: const Duration(milliseconds: 500),
  vsync: this,
);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);

以下示例展示了一个控制器、一个曲线和一个 Tween 的结合使用:

dart
AnimationController controller = AnimationController(
  duration: const Duration(milliseconds: 500),
  vsync: this,
);
final Animation<double> curve = CurvedAnimation(
  parent: controller,
  curve: Curves.easeOut,
);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);

动画通知

#

一个 Animation 对象可以拥有 ListenerStatusListener,它们分别通过 addListener()addStatusListener() 定义。当动画值发生变化时,Listener 会被调用。Listener 最常见的行为是调用 setState() 来触发重建。当动画开始、结束、向前移动或向后移动时(由 AnimationStatus 定义),StatusListener 会被调用。

Codelabs、教程和文章

#

以下资源是开始学习 Flutter 动画框架的良好起点。每份文档都展示了如何编写动画代码。

  • Flutter 动画 Codelab
    在构建多项选择测验游戏的同时,学习隐式和显式动画。

  • 动画教程
    解释了 Flutter 动画包中的基本类(控制器、Animatable、曲线、监听器、构建器),并引导你完成一系列使用不同动画 API 的补间动画。本教程展示了如何创建自定义显式动画。

  • Flutter 从零到一,第一部分第二部分
    Medium 文章,展示如何使用补间制作动画图表。

  • 休闲游戏工具包
    包含游戏模板的工具包,其中包含如何使用 Flutter 动画的示例。

其他资源

#

通过以下链接了解更多关于 Flutter 动画的信息: