Skip to main content

阶梯动画

:::次要内容 你将学习到

  • 阶梯动画由一系列顺序或重叠的动画组成。
  • 要创建阶梯动画,请使用多个 Animation 对象。
  • 一个 AnimationController 控制所有 Animation
  • 每个 Animation 对象在 Interval 中指定动画。
  • 对于每个正在动画化的属性,创建一个 Tween。 :::

:::提示 术语 如果您不了解补间动画或补间概念,请参阅 Flutter 动画教程。 :::

阶梯动画是一个简单的概念:视觉变化发生在一系列操作中,而不是一次性完成。动画可能是纯粹的顺序动画,一个变化发生在另一个变化之后,也可能是部分或完全重叠的。它也可能存在间隙,在间隙中没有任何变化发生。

本指南介绍如何在 Flutter 中构建阶梯动画。

:::次要内容 示例 本指南解释了 basic_staggered_animation 示例。 您还可以参考一个更复杂的示例,staggered_pic_selection。

basic_staggered_animation
显示单个小部件的一系列顺序和重叠动画。点击屏幕开始动画,该动画会改变不透明度、大小、形状、颜色和填充。
staggered_pic_selection
显示从以三种尺寸之一显示的图像列表中删除图像。此示例使用两个 动画控制器:一个用于图像选择/取消选择,一个用于图像删除。选择/取消选择动画是交错的。(要查看此效果,您可能需要增加 timeDilation 值。)选择其中一张最大的图像——它在显示蓝色圆圈内的复选标记时会缩小。接下来,选择其中一张最小的图像——大图像会在复选标记消失时展开。在大图像完全展开之前,小图像会缩小以显示其复选标记。这种交错行为类似于您在 Google 相册中可能看到的行为。 :::

以下视频演示了 basic_staggered_animation 执行的动画:


在视频中,您可以看到单个小部件的以下动画,该动画最初是一个带有略微圆角的蓝色边框正方形。正方形按以下顺序进行更改:

  1. 淡入
  2. 变宽
  3. 在向上移动的同时变高
  4. 变成一个带边框的圆圈
  5. 颜色变为橙色

正向运行后,动画反向运行。

:::次要内容 Flutter 新手? 此页面假设您知道如何使用 Flutter 的小部件创建布局。更多信息,请参见 在 Flutter 中构建布局。 :::

阶梯动画的基本结构

#

:::次要内容 重点是什么?

  • 所有动画都由同一个 AnimationController 驱动。
  • 无论动画在实际时间内持续多长时间,控制器的值都必须介于 0.0 和 1.0(含)之间。
  • 每个动画都有一个 Interval,其值介于 0.0 和 1.0(含)之间。
  • 对于在区间内动画化的每个属性,创建一个 TweenTween 指定该属性的起始值和结束值。
  • Tween 生成一个由控制器管理的 Animation 对象。 :::

下图显示了 basic_staggered_animation 示例中使用的 Interval。您可能会注意到以下特征:

  • 不透明度在时间线的最初 10% 内发生变化。
  • 在不透明度变化和宽度变化之间存在一个小小的间隙。
  • 在时间线的最后 25% 内没有任何动画。
  • 增加填充使小部件看起来向上移动。
  • 将边框半径增加到 0.5,会将带有圆角的正方形转换为圆形。
  • 填充和高度变化发生在完全相同的区间内,但它们不必如此。

显示为每个动作指定的区间的图表

要设置动画:

  • 创建一个 AnimationController 来管理所有 Animation
  • 为每个正在动画化的属性创建一个 Tween
    • Tween 定义一个值范围。
    • Tweenanimate 方法需要 parent 控制器,并为该属性生成一个 Animation
  • Animationcurve 属性上指定区间。

当控制动画的值发生变化时,新动画的值也会发生变化,从而触发 UI 更新。

以下代码为 width 属性创建一个补间。它构建了一个 CurvedAnimation,并指定了一个缓动曲线。请参阅 Curves 以了解其他可用的预定义动画曲线。

dart
width = Tween<double>(
  begin: 50.0,
  end: 150.0,
).animate(
  CurvedAnimation(
    parent: controller,
    curve: const Interval(
      0.125,
      0.250,
      curve: Curves.ease,
    ),
  ),
),

beginend 值不必是双精度数。以下代码使用 BorderRadius.circular()borderRadius 属性(控制正方形角的圆度)构建补间。

dart
borderRadius = BorderRadiusTween(
  begin: BorderRadius.circular(4),
  end: BorderRadius.circular(75),
).animate(
  CurvedAnimation(
    parent: controller,
    curve: const Interval(
      0.375,
      0.500,
      curve: Curves.ease,
    ),
  ),
),

完整的阶梯动画

#

与所有交互式小部件一样,完整的动画由一对小部件组成:一个无状态小部件和一个有状态小部件。

无状态小部件指定 Tween,定义 Animation 对象,并提供一个 build() 函数,该函数负责构建小部件树的动画部分。

有状态小部件创建控制器,播放动画,并构建小部件树的非动画部分。当在屏幕上的任何位置检测到点击时,动画开始。

basic_staggered_animation 的 main.dart 的完整代码

无状态小部件:StaggerAnimation

#

在无状态小部件 StaggerAnimation 中,build() 函数实例化了一个 AnimatedBuilder—一个用于构建动画的通用小部件。AnimatedBuilder 使用 Tween 的当前值构建一个小部件并对其进行配置。该示例创建一个名为 _buildAnimation() 的函数(执行实际的 UI 更新),并将其赋值给其 builder 属性。AnimatedBuilder 侦听来自动画控制器的通知,在值更改时将小部件树标记为脏。对于动画的每个刻度,值都会更新,从而导致调用 _buildAnimation()

dart
class StaggerAnimation extends StatelessWidget {
  StaggerAnimation({super.key, required this.controller}) :

    // 此处定义的每个动画都在由动画的区间定义的控制器的持续时间的子集中转换其值。
    // 例如,不透明度动画在控制器持续时间的最初 10% 内转换其值。

    opacity = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(
          0.0,
          0.100,
          curve: Curves.ease,
        ),
      ),
    ),

    // ... 其他补间定义 ...
    );

  final AnimationController controller;
  final Animation<double> opacity;
  final Animation<double> width;
  final Animation<double> height;
  final Animation<EdgeInsets> padding;
  final Animation<BorderRadius?> borderRadius;
  final Animation<Color?> color;

  // 每当控制器“刻度”一个新帧时,都会调用此函数。
  // 当它运行时,所有动画的值都将更新以反映控制器的当前值。
  Widget _buildAnimation(BuildContext context, Widget? child) {
    return Container(
      padding: padding.value,
      alignment: Alignment.bottomCenter,
      child: Opacity(
        opacity: opacity.value,
        child: Container(
          width: width.value,
          height: height.value,
          decoration: BoxDecoration(
            color: color.value,
            border: Border.all(
              color: Colors.indigo[300]!,
              width: 3,
            ),
            borderRadius: borderRadius.value,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      builder: _buildAnimation,
      animation: controller,
    );
  }
}

有状态小部件:StaggerDemo

#

有状态小部件 StaggerDemo 创建 AnimationController(控制它们所有的小部件),指定 2000 毫秒的持续时间。它播放动画,并构建小部件树的非动画部分。当在屏幕上检测到点击时,动画开始。动画正向运行,然后反向运行。

dart
class StaggerDemo extends StatefulWidget {
  @override
  State<StaggerDemo> createState() => _StaggerDemoState();
}

class _StaggerDemoState extends State<StaggerDemo>
    with TickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
  }

  // ...样板代码...

  Future<void> _playAnimation() async {
    try {
      await _controller.forward().orCancel;
      await _controller.reverse().orCancel;
    } on TickerCanceled {
      // 动画被取消了,可能是因为它被释放了。
    }
  }

  @override
  Widget build(BuildContext context) {
    timeDilation = 10.0; // 1.0 是正常的动画速度。
    return Scaffold(
      appBar: AppBar(
        title: const Text('Staggered Animation'),
      ),
      body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          _playAnimation();
        },
        child: Center(
          child: Container(
            width: 300,
            height: 300,
            decoration: BoxDecoration(
              color: Colors.black.withValues(alpha: 0.1),
              border: Border.all(
                color: Colors.black.withValues(alpha: 0.5),
              ),
            ),
            child: StaggerAnimation(controller:_controller.view),
          ),
        ),
      ),
    );
  }
}