Skip to content

flutter_animation_basics

动画通过提供视觉反馈、引导焦点以及创建更动态和引人入胜的界面来增强用户体验。Flutter 提供了一个健壮且灵活的动画系统,既支持简单的预设动画,也支持复杂的自定义动画。

Flutter 的动画系统围绕这些核心思想构建:

  • Animation: 表示类型为 T 的动画值的核心类(通常是 double,但也可是 Color、Size 等)。它知道动画的当前值,但不知道 UI 的状态或正在动画什么。它的值会随时间变化。
  • AnimationController: 管理动画播放。它是一个特殊的 Animation<double>,在指定的 duration 内生成从 0.0 到 1.0 的值。它允许你开始 (forward())、停止 (stop())、反向 (reverse()),并跟踪动画的状态 (AnimationStatus:dismissed, forward, reverse, completed)。需要一个 TickerProvider (vsync)。
  • TickerProvider (vsync): 提供 Ticker 对象。Ticker 在需要渲染新帧时发出信号,将动画与屏幕刷新率同步。StatefulWidget 的 State 对象可以实现 TickerProviderStateMixin 来充当 vsync。
  • Tween: 定义 AnimationController 的 0.0-1.0 范围与 UI 属性所需实际值范围之间的映射(例如,大小从 50 到 150,颜色从红色到蓝色)。它不存储状态;它只定义插值 begin 和 end 值。
  • Curve: 修改动画的变化速率,使其非线性(例如,缓入、弹性回弹)。使用 CurvedAnimation 应用于 AnimationController。

创建显式动画(即你管理控制器)的一般过程包括:

  1. 创建 AnimationController: 通常在 StatefulWidget 的 initState 中。需要 duration 和 vsync(由 TickerProviderStateMixin 提供)。
  2. 创建 Tween: 为你想要动画的属性定义开始和结束值。
  3. 组合 Controller 和 Tween: 使用 Tween.animate() 方法,传入 AnimationController(或包装控制器的 CurvedAnimation),创建一个 Animation<T> 对象。此对象将生成插值值。
  4. 监听值变化: 使用 addListener() 向 Animation 对象添加监听器。在监听器内部,调用 setState({}) 以便在动画值变化时触发组件重建。
  5. 使用动画值: 在 build 方法中,使用 animation.value 来设置你正在动画的组件属性(例如,width: sizeAnimation.value,color: colorAnimation.value)。
  6. 启动动画: 调用 controller.forward()、controller.repeat() 等方法。
  7. 释放 Controller: 至关重要的是,在 State 的 dispose 方法中释放 AnimationController,以防止资源泄露。

使用 AnimatedWidget 和 AnimatedBuilder 简化

Section titled “使用 AnimatedWidget 和 AnimatedBuilder 简化”

手动调用 addListener 和 setState 可能很繁琐。Flutter 提供了辅助组件:

  • AnimatedWidget: 基于给定 Animation 进行动画的组件基类。你可以继承 AnimatedWidget,将 Animation 传递给其构造函数的 super(listenable: animation),然后直接在 build 方法中使用 animation.value,无需 addListener 或 setState。对于创建可复用的动画组件很有用。
  • AnimatedBuilder: 用于构建动画的更通用的组件。它接受一个 animation 和一个 builder 函数。builder 函数接收 BuildContext 和一个可选的 child 组件。AnimatedBuilder 监听动画并在每一帧(frame tick)调用 builder 函数。它很高效,因为它只重建 builder 返回的组件树部分,而不是包含 builder 的整个组件。对于在更大的 build 方法中实现的特定、不可复用的动画,它通常比 AnimatedWidget 更受欢迎。

使用 AnimatedBuilder 的示例(淡入淡出动画)

Section titled “使用 AnimatedBuilder 的示例(淡入淡出动画)”
import 'package:flutter/material.dart';
class FadeLogo extends StatefulWidget {
const FadeLogo({Key? key}) : super(key: key);
@override
_FadeLogoState createState() => _FadeLogoState();
}
// 需要 TickerProviderStateMixin 提供 vsync
class _FadeLogoState extends State<FadeLogo> with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// 创建一个用于透明度(0.0 到 1.0)的 Tween
final opacityTween = Tween<double>(begin: 0.0, end: 1.0);
// 选项 1:线性动画
// _opacityAnimation = opacityTween.animate(_controller);
// 选项 2:带曲线的动画
_opacityAnimation = opacityTween.animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn)
);
// 启动动画
_controller.forward();
// 可选:使其重复
// _controller.repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose(); // 重要:释放 controller
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _opacityAnimation, // 监听此动画
builder: (context, child) {
// 此 builder 函数在每个动画帧(tick)运行时都会执行
return Opacity(
opacity: _opacityAnimation.value, // 使用当前的动画值
child: child, // 将 child 组件传递通过
);
},
child: const FlutterLogo(size: 150), // 正在进行动画的组件(传递给 builder)
),
);
}
}
// --- 使用示例 ---
// void main() => runApp(MaterialApp(home: Scaffold(body: FadeLogo())));

对于更简单的动画,如果你想在一定 duration 内动画一个属性的变化,Flutter 提供了隐式动画组件。这些组件会自动为其属性的变化添加动画效果。

例子包括:

  • AnimatedContainer: 对其 width、height、color、padding、margin、decoration、transform 等属性的变化进行动画。
  • AnimatedOpacity: 对其 opacity 的变化进行动画。
  • AnimatedPositioned: 对其在 Stack 中的位置变化进行动画。
  • AnimatedPadding: 对其 padding 的变化进行动画。
  • AnimatedTheme: 对主题变化进行动画。
  • AnimatedDefaultTextStyle: 对默认文本样式变化进行动画。

你只需在 setState 调用中改变目标属性的值,组件内部就会自动处理动画控制器和补间动画(tweening)。

import 'package:flutter/material.dart';
import 'dart:math';
class AnimatedBox extends StatefulWidget {
const AnimatedBox({Key? key}) : super(key: key);
@override
_AnimatedBoxState createState() => _AnimatedBoxState();
}
class _AnimatedBoxState extends State<AnimatedBox> {
double _width = 100.0;
double _height = 100.0;
Color _color = Colors.blue;
BorderRadiusGeometry _borderRadius = BorderRadius.circular(8.0);
void _animateBox() {
setState(() {
final random = Random();
_width = random.nextDouble() * 200 + 50; // Random width 50-250
_height = random.nextDouble() * 200 + 50; // Random height 50-250
_color = Color.fromRGBO(
random.nextInt(256),
random.nextInt(256),
random.nextInt(256),
1,
);
_borderRadius = BorderRadius.circular(random.nextDouble() * 50);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Implicit Animation')),
body: Center(
child: AnimatedContainer(
// 提供需要进行动画的属性
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: _borderRadius,
),
// 指定动画的 duration 和 curve
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn, // 示例 curve
child: Center(child: Text('Animate Me!', style: TextStyle(color: Colors.white))),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _animateBox,
child: const Icon(Icons.play_arrow),
),
);
}
}
// --- 使用示例 ---
// void main() => runApp(MaterialApp(home: AnimatedBox()));

隐式动画非常适合简单的、由状态驱动的 UI 过渡,无需编写显式动画控制器的样板代码。

  • 对于由状态更新触发的简单属性变化,使用隐式动画。
  • 对于更复杂的序列、可重复的动画、对播放的精细控制,或者当需要动画的属性不在隐式组件涵盖范围内时,使用显式动画(配合 AnimatedBuilder 或 AnimatedWidget)。

Flutter 的动画系统功能强大。请查阅 flutter.dev 上丰富的文档和示例,了解基于物理的动画、交错动画、英雄动画(页面过渡)等更多内容。