Flutter 显示动画,一篇就懂:从原理到实战,看完再也不晕
核心流水线:控制器生成0~1进度 → 曲线修饰节奏 → Tween转换业务值 → Animation存储值 → AnimatedBuilder渲染UI;性能关键:用实现局部重建,静态内容通过child复用,多动画用合并监听;避坑指南:务必释放,避免在builder内创建新对象,Tween需绑定后才能读取value。掌握显示动画的核心逻辑后,你可以轻松实现任意自定义动效——从简单的缩放渐变,到复杂的
Flutter 显示动画,一篇就懂:从原理到实战,看完再也不晕
显示动画的本质是「数值流转+信号驱动」
完整流水线如下:
AnimationController(0~1基础进度)
↓ (节奏调整)
CurvedAnimation(0~1带曲线进度)
↓ (数值转换)
Tween(0~1 → 业务值:如50~300尺寸/红→蓝颜色)
↓ (存储结果)
Animation(实时存储业务值)
↓ (渲染UI)
AnimatedBuilder(读取value,局部重建UI)
整体总结:控制器生成0~1进度 → 曲线修饰节奏 → Tween转换业务值 → Animation存储值 → AnimatedBuilder渲染UI
一、用「拍电影」比喻所有核心类(一看就懂)
我直接把整套体系换成你能立刻理解的角色:
| Flutter 动画类 | 电影里的角色 | 一句话作用 | 解释 |
|---|---|---|---|
| AnimationController | 导演 + 场记板 | 控制:开始、暂停、倒放、时长,只输出 0~1 的进度 | 每 16ms 刷新一帧,输出一个 0~1 的进度 → 这是唯一动力源 |
| CurvedAnimation | 运镜节奏 | 让动画不呆板:快进、慢放、回弹、缓入缓出(还是 0~1) | 把 0~1 的直线进度,掰成好看的曲线进度 → 还是 0~1,只是节奏变了 |
| Tween | 美术设定 / 剧本 | 把 0~1 转成真正的数值:大小、颜色、位置、圆角 | 把0~1的值转成实际的拍摄 |
| Animation | 实时画面帧 | 存当前这一帧的最终数值,给 UI 用 | Tween调用animate把拍摄记录成实时的帧 |
| AnimatedBuilder | 放映机 | 只刷新动的部分,不卡、不浪费性能 |
一句话总结全文(最强记忆版)
- AnimationController 提供 0~1 进度
- CurvedAnimation 调整节奏
- Tween 翻译成真实数值
- Animation 存当前帧
- AnimatedBuilder 高性能渲染
整条链路:进度 → 节奏 → 翻译 → 存值 → 显示
再一个例子,我们把显示动画比作「制作一杯奶茶」,瞬间理解核心角色:
| Flutter动画核心类 | 奶茶制作中的角色 | 核心作用 |
|---|---|---|
| AnimationController | 奶茶店店员 | 掌控全局:启动/暂停/停止动画,决定制作时长 |
| CurvedAnimation | 搅拌手法(匀速/慢快/弹性) | 修饰过程:让动画节奏更自然 |
| Tween(Animatable) | 配方(糖度0→100%、温度20→80℃) | 转换数值:把基础进度转成实际业务值 |
| Animation | 进度表 | 记录结果:显示当前动画的实时值 |
| AnimatedBuilder | 出杯台 | 展示成品:根据进度渲染最终UI |
显示动画的核心特征是需要开发者显式控制动画生命周期,区别于只需配置参数的隐式动画(如AnimatedContainer),适合实现自定义、多属性联动、精准控制的动效(比如路径动画、弹性缩放、颜色渐变)。
二、核心类展开
1. AnimationController —— 整部动画的“导演”
- 它只干一件事:按时间输出 0~1
- 控制动画:
forward()、reverse()、stop()、repeat() - 必须绑定
vsync: this,跟着屏幕刷新率走 - 必须
dispose(),否则内存泄漏
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
2. CurvedAnimation —— 动画的“节奏师”
- 输入:0~1
- 输出:曲线处理后的 0~1
- 不改变范围,只改变快慢手感
final curve = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
3. Tween —— 数值“翻译官”
- 控制器只会输出 0~1,界面看不懂
- Tween 负责翻译:
0~1 → 你想要的任何值
常用:
Tween<double>大小、透明度ColorTween颜色SizeTween尺寸OffsetTween位置BorderRadiusTween圆角
Animation<double> _sizeAnim = Tween<double>(
begin: 50,
end: 300,
).animate(curve);
4. Animation —— 最终“帧画面”
- 它不是控制器,不是 Tween
- 它就是:当前这一帧的结果
- 你 UI 里用的永远是:
animation.value
5. AnimatedBuilder —— 高性能“放映机”
- 不用
setState狂刷页面 - 只刷新动画相关部分
- 静态内容丢
child里,完全不重建
AnimatedBuilder(
animation: _controller,
child: Text("我不动,不重建"),
builder: (context, child) {
return Container(
width: _sizeAnim.value,
child: child,
);
},
)
6. Listenable.merge —— 多镜头同步拍摄
- 多个动画(大小、颜色、圆角)一起动
- 用它合并监听,一个 AnimatedBuilder 搞定
Listenable.merge([
_scaleAnim,
_colorAnim,
_radiusAnim,
])
三、核心类详细版本
1 动力源:AnimationController
AnimationController是整个动画的「发动机」,核心作用是定时生成0~1的进度值并发送信号:
- 初始化时绑定
vsync(垂直同步),保证只在屏幕刷新时计算进度(60帧/秒,约16ms一次),避免后台无效计算; - 通过
forward()/reverse()/stop()等方法控制动画播放状态; - 每计算一个新进度值,就触发「值变化信号」,通知所有关联对象更新。
基础用法:
// 初始化控制器
late AnimationController _controller;
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, // 混入SingleTickerProviderStateMixin
duration: const Duration(seconds: 2), // 动画时长
lowerBound: 0.0, // 最小值(默认0)
upperBound: 1.0, // 最大值(默认1)
);
// 监听进度变化
_controller.addListener(() {
print('当前进度:${_controller.value}');
});
// 监听状态变化
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse(); // 完成后反向播放
}
});
}
// 务必释放资源
void dispose() {
_controller.dispose();
super.dispose();
}
2 节奏调节器:CurvedAnimation
让动画摆脱匀速的「机械感」,通过曲线修饰进度变化节奏:
// 给控制器添加弹性曲线
CurvedAnimation _curve = CurvedAnimation(
parent: _controller,
curve: Curves.bounceOut, // 弹性结束
);
Flutter内置了丰富的曲线(如Curves.easeInOut先慢后快再慢、Curves.elasticIn弹性进入),也支持自定义曲线。
3 数值转换器:Tween
Tween是「规则手册」,负责把控制器输出的0~1标准化值,转换成业务需要的数值(尺寸、颜色、位置等),核心特点:
- 无状态:仅定义映射规则,不存储实时值;
- 需绑定:必须通过
animate()绑定到AnimationController/CurvedAnimation,生成有状态的Animation对象; - 多类型:Flutter内置
ColorTween、SizeTween、OffsetTween等常用子类。
基础用法:
// 尺寸补间:0~1 → 50~300
Animation<double> _sizeAnim = Tween<double>(
begin: 50.0,
end: 300.0,
).animate(_curve);
// 颜色补间:0~1 → 红→蓝
Animation<Color?> _colorAnim = ColorTween(
begin: Colors.red,
end: Colors.blue,
).animate(_curve);
进阶玩法:
- 自定义Tween:处理特殊类型值
class TextStyleTween extends Tween<TextStyle> {
TextStyleTween({required TextStyle begin, required TextStyle end})
: super(begin: begin, end: end);
TextStyle lerp(double t) {
return TextStyle(
fontSize: lerpDouble(begin!.fontSize, end!.fontSize, t),
color: Color.lerp(begin!.color, end!.color, t),
);
}
}
- 链式Tween:多步数值转换
// 0~1 → 0~100 → 50~150
Animation<double> _chainAnim = Tween<double>(begin: 0, end: 1)
.chain(Tween<double>(begin: 0, end: 100))
.chain(Tween<double>(begin: 50, end: 150))
.animate(_controller);
4 高性能渲染:AnimatedBuilder
AnimatedBuilder是显示动画的「最佳拍档」,核心价值是局部重建UI,避免整个页面随动画重绘:
- 只重建
builder回调内的组件,静态内容可通过child参数复用; - 底层监听动画信号,自动处理重建逻辑,无需手动调用
setState。
基础用法:
AnimatedBuilder(
animation: _controller,
// 静态子组件:只创建一次,不随动画重建
child: const Text('静态文本', style: TextStyle(color: Colors.white)),
builder: (context, child) {
return Container(
width: _sizeAnim.value,
height: 100,
color: _colorAnim.value,
child: child, // 复用静态子组件
);
},
)
多动画联动:Listenable.merge
当需要同时监听多个动画时,Listenable.merge可将多个「信号源」合并为一个,简化监听逻辑:
AnimatedBuilder(
// 合并缩放、颜色、圆角动画
animation: Listenable.merge([_sizeAnim, _colorAnim, _radiusAnim]),
builder: (context, child) {
return Container(
width: _sizeAnim.value,
height: _sizeAnim.value,
color: _colorAnim.value,
borderRadius: _radiusAnim.value,
child: child,
);
},
)
Listenable.merge的核心逻辑是「信号转发」:任意子动画触发信号,都会通知合并后的总信号源,只需一个AnimatedBuilder即可监听所有变化。
四、完整实战示例:多属性联动动画
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: ExplicitAnimationDemo()));
class ExplicitAnimationDemo extends StatefulWidget {
const ExplicitAnimationDemo({super.key});
State<ExplicitAnimationDemo> createState() => _ExplicitAnimationDemoState();
}
class _ExplicitAnimationDemoState extends State<ExplicitAnimationDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnim;
late Animation<Color?> _colorAnim;
late Animation<BorderRadius?> _radiusAnim;
late Listenable _mergedAnim;
void initState() {
super.initState();
// 1. 初始化控制器
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
// 2. 添加曲线修饰
final curve = CurvedAnimation(parent: _controller, curve: Curves.bounceOut);
// 3. 定义Tween动画
_scaleAnim = Tween<double>(begin: 0.8, end: 1.5).animate(curve);
_colorAnim = ColorTween(begin: Colors.blue, end: Colors.orange).animate(curve);
_radiusAnim = BorderRadiusTween(
begin: BorderRadius.circular(10),
end: BorderRadius.circular(50),
).animate(curve);
// 4. 合并多动画
_mergedAnim = Listenable.merge([_scaleAnim, _colorAnim, _radiusAnim]);
// 5. 循环播放动画
_controller.repeat(reverse: true);
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter显示动画实战')),
body: Center(
child: AnimatedBuilder(
animation: _mergedAnim,
child: const Text(
'多属性联动',
style: TextStyle(color: Colors.white, fontSize: 20),
),
builder: (context, child) {
return Transform.scale(
scale: _scaleAnim.value,
child: Container(
width: 200,
height: 200,
color: _colorAnim.value,
borderRadius: _radiusAnim.value,
child: child, // 重复利用child,不用每次都重建
),
);
},
),
),
);
}
}
核心总结
- 核心流水线:控制器生成0~1进度 → 曲线修饰节奏 → Tween转换业务值 → Animation存储值 → AnimatedBuilder渲染UI;
- 性能关键:用
AnimatedBuilder实现局部重建,静态内容通过child复用,多动画用Listenable.merge合并监听; - 避坑指南:务必释放
AnimationController,避免在builder内创建新对象,Tween需绑定后才能读取value。
掌握显示动画的核心逻辑后,你可以轻松实现任意自定义动效——从简单的缩放渐变,到复杂的路径动画、物理动效,Flutter显示动画的灵活性足以支撑所有场景的需求。
更多推荐
所有评论(0)