Flutter 动画的核心思想
优先选择隐式动画:对于简单的UI状态变化,始终先考虑等隐式动画,它们代码最少,最易于维护。需要控制时使用显式动画:当你需要循环、暂停或更复杂的动画序列时,就采用“Controller + Tween + AnimatedBuilder”的显式动画模式。性能优化:始终使用来包裹你的动画部分,避免不必要的setState导致整个页面重建。释放资源:永远不要忘记在State的dispose方法中调用,否
Flutter 的动画系统是其核心优势之一,它非常强大、灵活且性能卓越。我会为你全面解析 Flutter 动画,从最简单的入门到高级复杂的应用。
与许多UI框架不同,Flutter 的动画系统不是基于“开始状态”和“结束状态”的简单过渡。它的核心是 Animation<T> 对象。
Animation<T> 是一个不关心UI的对象,它只知道在一段时间内,它的值 T(比如 double, Color, Size)会如何变化。你可以监听这个值的变化,然后用它来驱动任何你想要的东西。
两大类动画:隐式动画 vs 显式动画
Flutter 的动画可以分为两大阵营,理解它们的区别是掌握动画的关键。
| 特性 | 隐式动画 (Implicitly Animated Widgets) | 显式动画 (Explicit Animations) |
|---|---|---|
| 核心思想 | 状态驱动,自动执行 | 手动控制,完全掌握 |
| 使用难度 | 非常简单 | 较复杂,但更强大 |
| 控制力 | 有限(只能定义时长和曲线) | 完全控制(播放、暂停、反向、循环) |
| 典型代表 | AnimatedContainer, AnimatedOpacity |
AnimationController, Tween, AnimatedBuilder |
| 适用场景 | 简单的、一次性的UI状态变化 | 复杂的、可重复的、需要精细控制的动画 |
1. 隐式动画 (Implicitly Animated Widgets) - 最简单的入门
这是最快、最简单的添加动画的方式。你只需要使用那些以 Animated 开头的 Widget,然后改变它们的属性,动画就会自动发生。
核心原理
-
你提供一个目标值(比如新的宽度、颜色)。
-
你提供一个
duration(动画时长) 和一个curve(动画曲线)。 -
当
setState触发 Widget 重建时,它会发现目标值变了,然后自动为你生成从当前值到目标值的平滑过渡动画。
最常用的隐式动画 Widget:
-
AnimatedContainer: 一个万能的动画容器。可以动画化width,height,color,padding,margin,decoration,transform等几乎所有属性。 -
AnimatedOpacity: 动画化子 Widget 的透明度。 -
AnimatedPositioned: 在Stack布局中,动画化子 Widget 的位置。 -
AnimatedCrossFade: 在两个子 Widget 之间进行平滑的淡入淡出切换。
示例:一个点击后会变大变色的方块
import 'package:flutter/material.dart';
class ImplicitAnimationExample extends StatefulWidget {
const ImplicitAnimationExample({super.key});
@override
State<ImplicitAnimationExample> createState() => _ImplicitAnimationExampleState();
}
class _ImplicitAnimationExampleState extends State<ImplicitAnimationExample> {
// 1. 定义状态变量
bool _isBig = false;
double _size = 100.0;
Color _color = Colors.blue;
void _toggleAnimation() {
// 2. 使用 setState 更新状态
setState(() {
_isBig = !_isBig;
_size = _isBig ? 200.0 : 100.0;
_color = _isBig ? Colors.red : Colors.blue;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('隐式动画')),
body: Center(
child: GestureDetector(
onTap: _toggleAnimation,
// 3. 使用 AnimatedContainer
child: AnimatedContainer(
// 4. 定义动画时长和曲线
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn, // 一个很舒服的缓动曲线
// 5. 将状态变量绑定到属性上
width: _size,
height: _size,
color: _color,
child: const Center(
child: Text('Click Me', style: TextStyle(color: Colors.white)),
),
),
),
),
);
}
}
解析: 你完全没有手动管理动画的播放。你做的仅仅是改变状态 (_size 和 _color),AnimatedContainer 就自动处理了剩下的一切。这就是隐式动画的魅力。
2. 显式动画 (Explicit Animations) - 完全的控制力
当你需要循环动画、暂停/继续、或者更复杂的动画序列时,就需要使用显式动画。它有几个核心组件:
-
Ticker: 动画的“心跳”,它以屏幕刷新率(如 60fps)稳定地发出信号,告诉动画该刷新下一帧了。通常通过混入TickerProviderStateMixin来获得。 -
AnimationController: 动画的“总指挥”。它在给定的duration内,生成一个从0.0到1.0的线性值。你可以命令它forward()(播放),reverse()(反向),stop()(停止),repeat()(循环)。 -
Tween: 值的“映射器”。它定义了一个值的范围(比如从100.0到200.0,或者从Colors.blue到Colors.red)。它使用AnimationController产生的0.0-1.0的值来计算出在这个范围内的具体值。 -
Animation: 持有动画当前值的对象。Tween和AnimationController通过.animate()方法结合后,就会产生一个Animation对象。 -
AnimatedBuilder: 构建动画UI的最佳实践。它监听AnimationController的变化,并且只重建需要动画的部分,从而获得最佳性能。
示例:一个无限放大缩小的呼吸效果方块
这个例子我们在上一个回答中已经详细解析过了,它完美地展示了显式动画的完整流程。这里再回顾一下它的核心代码结构:
// 1. 混入 TickerProviderStateMixin
class _ExplicitAnimationState extends State<ExplicitAnimationExample> with SingleTickerProviderStateMixin {
// 2. 声明 Controller 和 Animation
late AnimationController _controller;
late Animation<double> _sizeAnimation;
@override
void initState() {
super.initState();
// 3. 初始化 Controller
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
// 4. 使用 Tween 和 Controller 创建 Animation
_sizeAnimation = Tween<double>(begin: 100.0, end: 200.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
// 5. 添加监听器以实现循环
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
// 6. 启动动画
_controller.forward();
}
@override
Widget build(BuildContext context) {
// 7. 使用 AnimatedBuilder 高效构建UI
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
// 8. 从 Animation 对象中获取当前值
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: Colors.green,
);
},
);
}
@override
void dispose() {
// 9. 必须销毁 Controller
_controller.dispose();
super.dispose();
}
}
这个流程是所有复杂显式动画的基础。
以下是这段 Flutter 显式动画代码的详细逐行解释:
1. 混入 TickerProviderStateMixin
class _ExplicitAnimationState extends State<ExplicitAnimationExample> with SingleTickerProviderStateMixin {
-
SingleTickerProviderStateMixin是一个混入类,为动画提供vsync信号 -
它确保动画只在屏幕刷新时更新(通常60fps),避免不必要的资源消耗
-
当有多个控制器时,应使用
TickerProviderStateMixin
2. 声明动画控制器和动画对象
late AnimationController _controller;
late Animation<double> _sizeAnimation;
-
AnimationController控制动画的播放状态(开始/停止/反转)和进度 -
Animation<double>是一个动画对象,会在指定范围内生成连续的 double 值 -
late关键字表示这些变量将在初始化时被赋值
3. 初始化动画控制器
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
-
vsync: this使用混入的 TickerProvider 来同步屏幕刷新 -
duration设置动画完成一次正向播放的时间(2秒) -
控制器默认范围是 0.0 到 1.0
4. 创建动画曲线和值范围
_sizeAnimation = Tween<double>(begin: 100.0, end: 200.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
-
Tween定义动画值的范围(从100.0到200.0) -
CurvedAnimation应用非线性曲线(easeInOut)使动画更自然 -
最终
_sizeAnimation会生成从100到200的平滑变化值
5. 添加动画状态监听器实现循环
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
-
当动画完成(
completed)时自动反向播放 -
当动画回到起点(
dismissed)时再次正向播放 -
这样就创建了无限循环的动画效果
6. 启动动画
_controller.forward();
-
开始正向播放动画(从开始值到结束值)
7. 使用 AnimatedBuilder 构建UI
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: Colors.green,
);
},
);
-
AnimatedBuilder只重建动画依赖的部分,优化性能 -
当动画值变化时,自动调用 builder 方法重建UI
-
比在
setState中重建整个子树更高效
8. 获取当前动画值
width: _sizeAnimation.value,
height: _sizeAnimation.value,
-
_sizeAnimation.value获取当前帧的动画值 -
随着动画播放,这个值会在100.0到200.0之间平滑变化
9. 销毁控制器
_controller.dispose();
-
必须手动释放动画控制器资源
-
防止内存泄漏和后台不必要的计算
-
通常在
dispose()生命周期方法中调用
完整工作流程
-
初始化时创建控制器和动画配置
-
设置动画状态监听实现循环逻辑
-
启动动画
-
每帧根据动画曲线计算当前值
-
AnimatedBuilder 根据新值重建UI
-
组件销毁时释放资源
3. 特定场景的强大动画
除了上述两种基础类型,Flutter 还内置了一些非常惊艳的、针对特定场景的动画。
Hero 动画 (共享元素过渡)
当你在两个页面之间导航时,Hero 动画可以让两个页面中相同的元素(比如一张图片)平滑地过渡过去,效果非常酷炫。
使用方法:
-
在第一个页面的 Widget 外面包一个
HeroWidget,并给它一个唯一的tag。 -
在第二个页面的对应 Widget 外面也包一个
HeroWidget,并使用完全相同的tag。 -
使用
Navigator.push进行页面跳转。
// 页面A
Hero(
tag: 'avatar',
child: CircleAvatar(backgroundImage: AssetImage('...')),
);
// 页面B
Hero(
tag: 'avatar',
child: Image.asset('...'),
);
Flutter 会自动为你处理中间所有的位移、缩放动画。
4. 强大的动画库 (Packages)
当内置动画无法满足你的需求时,社区提供了许多优秀的动画库。
-
lottie-flutter: 神器!可以直接加载设计师用 Adobe After Effects 制作的复杂动画(导出为 JSON 文件)。对于实现非常炫酷、复杂的引导页、加载动画等,这是不二之选。 -
rive: 另一个强大的动画工具,允许你创建可以实时交互的、带有状态机的复杂动画。非常适合用于游戏角色、动态图标等。
总结与最佳实践
-
优先选择隐式动画:对于简单的UI状态变化,始终先考虑
AnimatedContainer等隐式动画,它们代码最少,最易于维护。 -
需要控制时使用显式动画:当你需要循环、暂停或更复杂的动画序列时,就采用“Controller + Tween + AnimatedBuilder”的显式动画模式。
-
性能优化:始终使用
AnimatedBuilder来包裹你的动画部分,避免不必要的setState导致整个页面重建。 -
释放资源:永远不要忘记在
State的dispose方法中调用_controller.dispose(),否则会造成内存泄漏。 -
善用曲线 (
Curve):不要总是用线性动画,选择合适的Curve(如Curves.easeInOut)会让你的动画看起来更自然、更专业。 -
页面过渡用
Hero:在页面切换时,Hero动画能极大地提升用户体验的连贯性。
更多推荐



所有评论(0)