Skip to content

Flutter - 动画

动画通过添加运动和视觉反馈来增强用户体验,使应用感觉更具交互性和精致。虽然动画可能很复杂,但 Flutter 提供了一个强大而直观的动画框架。

动画本质上是通过在特定持续时间内显示一系列帧来创建的运动幻觉。关键方面包括:

  • 起始值和结束值(Start and End Values): 动画在定义的起始值和结束值之间进行插值(interpolate)(例如,将透明度从 1.0(完全可见)动画到 0.0(不可见))。
  • 插值(Tweening): 计算起始值和结束值之间的中间值的过程。可以是线性的(恒定速度)或非线性的(使用 Curves 控制加速、减速、弹跳等)。
  • 持续时间(Duration): 动画完成的总时间,影响其感知速度。
  • 控制(Control): 启动、停止、反向或重复动画的能力。
  • Ticker: 为每一帧提供一个信号(一个 ‘tick’),将动画与屏幕刷新率同步。
  • Animation 对象(Animation Object): 在 Flutter 中,动画系统主要通过 Animation 对象随时间生成变化的值(如数字、颜色、大小)。它不直接渲染;widgets 在构建过程中使用这些值来更新其属性。

Flutter 的动画系统围绕几个核心类展开:

表示一个类型为 T 且随时间变化的值。它知道其当前值和状态(例如,运行中、已完成、已取消)。常见类型包括 Animation<double>、Animation<Color>、Animation<Size>。

一个特殊的 Animation<double>,用于管理动画过程。它在给定的持续时间内生成从 0.0 到 1.0 的值,并提供控制动画的方法(例如,forward() 前进、reverse() 反向、stop() 停止、repeat() 重复)。它需要一个 TickerProvider(vsync)。

// Inside a State class that mixes in TickerProviderStateMixin
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this, // 'this' provides the Ticker
);
}
@override
void dispose() {
_controller.dispose(); // IMPORTANT: Dispose the controller!
super.dispose();
}

vsync 防止屏幕外的动画消耗资源。它通常是通过将 TickerProviderStateMixin(用于单个 controller)或 SingleTickerProviderStateMixin(用于多个 controllers)混入(mixin)到你的 State 类中来提供的。

定义 AnimationController 的 0.0-1.0 范围与你的动画实际需要的值范围之间的映射关系(例如,将 0.0-1.0 映射到 0-300 像素,或映射到特定颜色)。它是 ‘in-between’ 的缩写。

// Creates an animation that goes from 50.0 to 200.0
late Animation<double> _sizeAnimation;
// In initState, after controller is initialized:
_sizeAnimation = Tween<double>(begin: 50.0, end: 200.0).animate(_controller);

将非线性 Curve(如 Curves.easeIn、Curves.bounceOut)应用于现有动画(通常是 AnimationController)。这会修改变化的速率,而不是值范围。

// Apply an ease-in curve to the controller
late Animation<double> _curvedAnimation;
// In initState, after controller is initialized:
_curvedAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
);
// You can then chain a Tween with the CurvedAnimation:
_sizeAnimation = Tween<double>(begin: 50.0, end: 200.0).animate(_curvedAnimation);

创建显式动画的典型工作流程包括:

  1. 设置(在 State 中):
    • 混入 TickerProviderStateMixin 或 SingleTickerProviderStateMixin。
    • 在 initState 中创建 AnimationController,提供 duration 和 vsync。
    • 创建 Tween 以定义值范围。
    • 通过使用 .animate() 将 Tween(以及可选的 CurvedAnimation)连接到 AnimationController 来创建 Animation 对象。
    • 重要: 在 dispose 方法中释放 AnimationController。
  2. 监听/构建(Listening/Building): 选择以下一种方法来在动画值改变时重建 UI:
    • AnimatedBuilder:(推荐)一个 widget,它监听一个动画,并在动画值改变时重建其 builder 函数。它通过仅影响其子树来优化重建。
    • AnimatedWidget: 用于创建可重用动画 widgets 的基类(如 FadeTransition、SlideTransition)。你扩展它并在其 build 方法中直接使用动画值。
    • addListener + setState: 手动向 Animation 或 AnimationController 添加监听器。在监听器回调函数内部,调用 setState(() {}) 来触发整个 widget 的重建。这不如 AnimatedBuilder 或 AnimatedWidget 高效。
  3. 使用值(Using the Value): 在 build 方法中(在 AnimatedBuilder 的 builder 中,或在 AnimatedWidget 中,或如果使用 addListener 则在主 build 中),使用 animation.value 访问动画的当前值,并将其应用于 widget 的属性(如 width、height、opacity、color、transform)。
  4. 启动动画(Starting the Animation): 调用 AnimationController 上的方法,如 _controller.forward() 或 _controller.repeat()。

让我们创建一个应用,让一个 logo 渐隐出现并放大。

  1. 创建一个新的 Flutter 项目:flutter create animation_example。
  2. 将 lib/main.dart 替换为以下代码:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Animation Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const AnimationDemoPage(),
);
}
}
class AnimationDemoPage extends StatefulWidget {
const AnimationDemoPage({Key? key}) : super(key: key);
@override
_AnimationDemoPageState createState() => _AnimationDemoPageState();
}
// Use SingleTickerProviderStateMixin since we have one AnimationController
class _AnimationDemoPageState extends State<AnimationDemoPage>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _opacityAnimation;
late Animation<double> _sizeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
// Opacity goes from 0.0 (invisible) to 1.0 (visible)
// Use a CurvedAnimation for a non-linear fade (e.g., ease-in)
final curvedAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
);
_opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(curvedAnimation);
// Size goes from 50 to 200
// You can animate the controller directly or use the curved animation
_sizeAnimation = Tween<double>(begin: 50.0, end: 200.0).animate(curvedAnimation);
// Start the animation automatically
_controller.forward();
/* Optional: Add listeners to track status
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// Maybe reverse it?
// _controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// Maybe start it again?
// _controller.forward();
}
});
*/
}
@override
void dispose() {
// IMPORTANT: Dispose the controller when the widget is removed
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Fade & Grow Animation'),
),
body: Center(
// Use AnimatedBuilder for efficient rebuilding
child: AnimatedBuilder(
animation: _controller, // Listen to the controller
builder: (BuildContext context, Widget? child) {
// This builder function runs every time the animation ticks
return Opacity(
opacity: _opacityAnimation.value, // Use the opacity value
child: Container(
width: _sizeAnimation.value, // Use the size value
height: _sizeAnimation.value,
child: child, // Pass the constant child down
),
);
},
// Provide a constant child that doesn't need rebuilding itself
child: const FlutterLogo(),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Example: Control the animation
if (_controller.isCompleted) {
_controller.reverse();
} else {
_controller.forward();
}
},
child: const Icon(Icons.play_arrow),
),
);
}
}

运行此应用。你会看到 Flutter logo 从小而透明的状态开始,然后在 3 秒内逐渐放大并渐隐出现。浮动操作按钮(Floating Action Button)可用于重播或反向播放动画。

Flutter 还提供了隐式动画(Implicit Animations)(如 AnimatedContainer、AnimatedOpacity 等 widget),它们会自动在指定持续时间内动画化其属性的变化,无需显式使用 controllers 或 tweens,从而简化了常见的动画任务。