4.1 动画 (Animations)

基础知识

动画是提升用户体验、使应用程序更具吸引力的重要组成部分。Flutter 提供了强大而灵活的动画系统,允许开发者创建各种复杂的动画效果。Flutter 的动画系统基于 AnimationAnimationControllerTweenCurve 等核心概念。

1. 核心概念

  • Animation<T> (动画对象):一个抽象类,表示一个随时间变化的动画值。它知道动画的当前值和状态(例如,是否正在运行、是否已完成)。Animation<double> 是最常见的动画类型,其值通常在 0.0 到 1.0 之间。
  • AnimationController (动画控制器):一个特殊的 Animation<double>,用于控制动画的生命周期,包括启动、停止、前进、反转、重复等。它需要一个 vsync 对象来同步动画与屏幕刷新。
  • Tween<T> (补间动画):定义动画的起始值和结束值,以及如何在这两个值之间进行插值。例如,Tween<double>(begin: 0.0, end: 1.0) 会在 0.0 和 1.0 之间插值。
  • Curve (曲线):定义动画的非线性运动。例如,Curves.easeOut 会使动画开始时快,结束时慢。
  • AnimatedBuilder (动画构建器):一个 Widget,用于在动画值变化时重建其子 Widget。它优化了性能,因为它只重建动画相关的部分,而不是整个 Widget 树。
  • 隐式动画 (Implicit Animations):Flutter 提供了一系列内置的隐式动画 Widget,它们会自动处理动画逻辑,你只需要改变它们的属性值,它们就会自动播放动画。例如 AnimatedContainerAnimatedOpacityAnimatedAlign 等。

2. 动画类型

  • 显式动画 (Explicit Animations):需要手动控制 AnimationController 的动画。适用于复杂的、自定义的动画。
  • 隐式动画 (Implicit Animations):Flutter 框架自动处理的动画。适用于简单的、常见的动画效果。

3. 显式动画的基本步骤

  1. 创建 AnimationController:在 StatefulWidgetState 中创建,并提供 vsync(通常是 this)。
  2. 定义 Tween:定义动画的起始和结束值。
  3. 连接 TweenAnimationController:使用 Tween.animate(controller) 创建一个 Animation 对象。
  4. 监听动画状态:使用 addListener() 监听动画值的变化,并调用 setState() 来重建 UI。或者使用 AnimatedBuilder
  5. 控制动画:调用 controller.forward()controller.reverse() 等方法来控制动画。
  6. 清理资源:在 dispose() 方法中释放 AnimationController
dart
复制代码
import 'package:flutter/material.dart';

class ExplicitAnimationExample extends StatefulWidget {
  const ExplicitAnimationExample({super.key});

  @override
  State<ExplicitAnimationExample> createState() => _ExplicitAnimationExampleState();
}

class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2), // 动画持续时间
      vsync: this, // 提供 vsync
    );

    _animation = Tween<double>(begin: 0.0, end: 200.0).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.easeOut, // 动画曲线
      ),
    );

    // 监听动画状态变化
    _animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse(); // 动画完成后反向播放
      } else if (status == AnimationStatus.dismissed) {
        _controller.forward(); // 动画回到起点后正向播放
      }
    });

    _controller.forward(); // 启动动画
  }

  @override
  void dispose() {
    _controller.dispose(); // 释放控制器资源
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('显式动画示例'),
      ),
      body: Center(
        // 使用 AnimatedBuilder 优化性能,只重建动画相关的部分
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Container(
              width: _animation.value, // 宽度随动画值变化
              height: _animation.value, // 高度随动画值变化
              color: Colors.blue,
              child: Center(
                child: Text(
                  _animation.value.toStringAsFixed(0),
                  style: const TextStyle(color: Colors.white, fontSize: 24),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: ExplicitAnimationExample()));
}

官方文档链接

Flutter 开发中的应用案例

动画在 Flutter 应用中无处不在,从简单的按钮点击反馈到复杂的页面过渡和元素变形,动画极大地提升了用户体验。理解并熟练运用 Flutter 的动画系统是构建高质量应用的关键。

案例:一个可展开/收起的面板 (使用隐式动画 AnimatedContainer)

我们将创建一个简单的可展开/收起的面板,当用户点击按钮时,面板的高度会自动平滑过渡。这个案例将演示如何使用 AnimatedContainer 实现隐式动画。

dart
复制代码
import 'package:flutter/material.dart';

class AnimatedContainerExample extends StatefulWidget {
  const AnimatedContainerExample({super.key});

  @override
  State<AnimatedContainerExample> createState() => _AnimatedContainerExampleState();
}

class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
  bool _isExpanded = false;
  double _height = 100.0; // 初始高度
  Color _color = Colors.blue; // 初始颜色

  void _toggleExpanded() {
    setState(() {
      _isExpanded = !_isExpanded;
      _height = _isExpanded ? 250.0 : 100.0; // 根据状态改变高度
      _color = _isExpanded ? Colors.red : Colors.blue; // 根据状态改变颜色
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedContainer 示例'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // AnimatedContainer 会自动在属性变化时播放动画
            AnimatedContainer(
              duration: const Duration(milliseconds: 500), // 动画持续时间
              curve: Curves.easeInOut, // 动画曲线
              width: 200.0,
              height: _height, // 动画目标高度
              color: _color, // 动画目标颜色
              alignment: Alignment.center,
              padding: const EdgeInsets.all(10.0),
              child: Text(
                _isExpanded ? '面板已展开' : '面板已收起',
                style: const TextStyle(color: Colors.white, fontSize: 20),
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _toggleExpanded,
              child: Text(_isExpanded ? '收起面板' : '展开面板'),
            ),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: AnimatedContainerExample()));
}

案例分析:

  • AnimatedContainer:这是一个隐式动画 Widget。当它的属性(如 heightcoloralignmentpadding 等)发生变化时,它会自动在当前值和新值之间进行平滑过渡,而无需手动管理 AnimationController
  • durationcurveAnimatedContainer 提供了 duration(动画持续时间)和 curve(动画曲线)属性,用于控制动画的速度和节奏。
  • _toggleExpanded() 方法:当按钮被点击时,_toggleExpanded 方法会改变 _isExpanded 状态,并根据新状态更新 _height_color 的值。由于这些值是 AnimatedContainer 的属性,Flutter 会自动触发动画。

这个案例清晰地展示了如何使用 Flutter 的隐式动画 Widget 来实现常见的动画效果。对于大多数简单的动画需求,隐式动画是更简洁、更高效的选择。对于更复杂的动画,例如自定义路径动画或多个动画同时进行的场景,则需要使用显式动画。