前言:
路过一条,不用深究,交互并不是vibecoding的核心,完成数据交互和页面展示才是真正的应用!
一、总览
本讲聚焦 Flutter 显式动画 的核心控制能力,是从基础动画(AnimatedContainer 等隐式动画)到自定义、高性能、组合式动画的进阶内容。通过学习本讲,你将掌握:
- AnimationController:动画的「总开关」,控制播放/暂停/反向,必须绑定vsync并手动释放
- Tween + Curve:将0.0~1.0的基础值映射到任意类型(尺寸/颜色/偏移),Curve让动画更自然
- AnimatedBuilder:优化动画性能,仅重建动画相关部分,静态内容放child参数
- 组合动画:通过Interval控制动画顺序,通过Matrix4组合多维度变换,实现复杂视觉效果
- AnimationController:动画的「发动机」,生成 0.0 ~ 1.0 的线性数值(绑定屏幕刷新率,默认60帧/秒)
- Tween:插值器,将 0.0~1.0 映射到自定义范围(如颜色、尺寸、位置)
- Animation:承载动画的实时值,可添加监听回调响应值变化
- vsync:防止动画在后台运行(节省性能),通常绑定到StatefulWidget的TickerProvider
- AnimatedBuilder:动画与UI解耦,仅重建需要动画的部分,而非整个组件树
二、 核心知识点
2.1 AnimationController(动画控制器)
核心作用
控制动画的播放状态(启动、暂停、反向、停止),生成基础的 0.0~1.0 动画值。
核心属性/方法
| 类别 | 名称 | 说明 |
|---|---|---|
| 初始化属性 | duration | 动画时长(必传) |
| vsync | 绑定TickerProvider(必传,防后台动画) | |
| lowerBound/upperBound | 动画值范围(默认0.0~1.0) | |
| initialValue | 初始值(默认0.0) | |
| 控制方法 | forward() | 正向播放(从当前值到upperBound) |
| reverse() | 反向播放(从当前值到lowerBound) | |
| pause()/resume() | 暂停/恢复 | |
| reset() | 重置到初始值 | |
| repeat() | 循环播放(可设置reverse参数) | |
| 监听方法 | addListener() | 监听值变化(每次值更新触发) |
| addStatusListener() | 监听状态变化(完成/暂停/反向等) |
基础案例
import 'package:flutter/material.dart';
// 主入口
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue),
home: const AnimationControllerDemo(),
);
}
}
class AnimationControllerDemo extends StatefulWidget {
const AnimationControllerDemo({super.key});
@override
State<AnimationControllerDemo> createState() =>
_AnimationControllerDemoState();
}
// 混入 TickerProviderStateMixin 提供 vsync
class _AnimationControllerDemoState extends State<AnimationControllerDemo>
with TickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
// 初始化控制器:时长2秒,绑定vsync
_controller = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
// 可选:设置值范围(默认0.0~1.0)
lowerBound: 0.1,
upperBound: 1.0,
);
// 监听动画状态变化
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 动画完成后反向播放
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// 动画回到初始状态后重新播放
_controller.forward();
}
});
}
@override
void dispose() {
// 必须释放控制器,避免内存泄漏
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AnimationController 演示')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 直接使用动画值(基础用法,性能一般)
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: _controller.value * 200,
height: _controller.value * 200,
color: Colors.red,
);
},
),
const SizedBox(height: 20),
// 控制按钮
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _controller.forward(), // 正向播放(0→1)
// 正向播放(0→1)
child: const Text('播放'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => _controller.stop(), // 暂停
child: const Text('暂停'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => _controller.reset(), // 重置到初始状态
child: const Text('重置'),
),
],
),
],
),
),
);
}
}
注意事项
- 必须释放:在State的dispose中调用
_controller.dispose(),否则会内存泄漏 - vsync绑定:必须混入
TickerProviderStateMixin(单控制器)或SingleTickerProviderStateMixin(多控制器) - 状态判断:AnimationStatus包含completed(完成)、dismissed(初始)、forward(正向播放)、reverse(反向播放)
2.2 Tween 插值 + Animation 监听
核心作用
- Tween:将AnimationController的0.0~1.0映射到任意类型的值(如颜色、偏移、尺寸)
- Animation:Tween与Controller结合后的「值容器」,提供监听能力
核心知识点
-
常用Tween类型:
Tween<double>:数值(尺寸、透明度)ColorTween:颜色OffsetTween:偏移(位移)BorderRadiusTween:圆角Matrix4Tween:3D变换
-
CurvedAnimation:给动画加「曲线」,让运动更符合物理规律:
Curves.linear:匀速(默认)Curves.ease:慢进快出Curves.bounceOut:回弹效果Curves.elasticIn:弹性进入
基础案例
import 'package:flutter/material.dart';
class TweenAnimationDemo extends StatefulWidget {
const TweenAnimationDemo({super.key});
@override
State<TweenAnimationDemo> createState() => _TweenAnimationDemoState();
}
class _TweenAnimationDemoState extends State<TweenAnimationDemo> with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _sizeAnim; // 尺寸动画
late Animation<Color?> _colorAnim; // 颜色动画
late Animation<Offset> _offsetAnim; // 偏移动画
@override
void initState() {
super.initState();
// 1. 初始化控制器
_controller = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
// 2. 定义Tween插值器,并绑定到控制器
_sizeAnim = Tween<double>(begin: 50, end: 200).animate(_controller);
_colorAnim = ColorTween(begin: Colors.red, end: Colors.blue).animate(_controller);
_offsetAnim = Tween<Offset>(
begin: const Offset(-1, 0), // 初始在屏幕左侧
end: const Offset(1, 0), // 最终到屏幕右侧
).animate(CurvedAnimation( // 加曲线,让动画更自然
parent: _controller,
curve: Curves.easeInOut, // 先慢后快再慢
));
// 3. 监听动画值变化(可选)
_sizeAnim.addListener(() {
// 手动触发重建(不推荐,建议用AnimatedBuilder)
// setState(() {});
print('当前尺寸:${_sizeAnim.value}');
});
// 4. 监听动画状态
_controller.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('Tween 插值演示')),
body: Center(
child: SlideTransition( // 偏移过渡组件
position: _offsetAnim,
child: Container(
width: _sizeAnim.value,
height: _sizeAnim.value,
color: _colorAnim.value,
),
),
),
);
}
}
注意事项:
- Tween本身不存储值,必须通过
animate()绑定到AnimationController - 直接用
addListener + setState会导致整个组件重建,性能差,优先用AnimatedBuilder - CurvedAnimation是对Animation的包装,不影响原控制器
2.3 AnimatedBuilder 优化重构
核心作用
将「动画逻辑」与「UI渲染」解耦,仅重建需要动画的部分,提升性能。
核心属性/注意事项
| 属性 | 说明 |
|---|---|
| animation | 绑定动画控制器/Animation(必传) |
| builder | 动画值变化时触发的构建函数 |
| child | 静态子组件(可选),避免重复重建 |
优化案例(重构上面的Tween案例)
import 'package:flutter/material.dart';
class AnimatedBuilderDemo extends StatefulWidget {
const AnimatedBuilderDemo({super.key});
@override
State<AnimatedBuilderDemo> createState() => _AnimatedBuilderDemoState();
}
class _AnimatedBuilderDemoState extends State<AnimatedBuilderDemo> with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _sizeAnim;
late Animation<Color?> _colorAnim;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
_sizeAnim = Tween<double>(begin: 50, end: 200).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_colorAnim = ColorTween(begin: Colors.red, end: Colors.blue).animate(_controller);
_controller.repeat(reverse: true); // 循环反向播放
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AnimatedBuilder 优化')),
body: Center(
// 核心:AnimatedBuilder 只重建builder内的内容
child: AnimatedBuilder(
animation: _controller, // 绑定动画控制器
builder: (context, child) {
// 仅这里的代码会在动画值变化时重建
return Container(
width: _sizeAnim.value,
height: _sizeAnim.value,
color: _colorAnim.value,
// 复用静态子组件(如果有)
child: child,
);
},
// 静态子组件:不会随动画重建
child: const Center(
child: Text(
'优化动画',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
),
);
}
}
注意事项:
- AnimatedBuilder 是「无状态组件」,不会触发父组件的setState
- 优先将静态内容放在
child参数中,减少重建开销 - 可以嵌套多个AnimatedBuilder实现多维度动画
2.4 显式动画与组合动画
核心概念
- 显式动画:开发者手动控制动画生命周期(如AnimationController),区别于隐式动画(AnimatedContainer等自动管理)
- 组合动画:多个动画协同工作(同时播放、顺序播放、叠加播放)
组合动画案例(顺序+叠加)
import 'package:flutter/material.dart';
class CombinedAnimationDemo extends StatefulWidget {
const CombinedAnimationDemo({super.key});
@override
State<CombinedAnimationDemo> createState() => _CombinedAnimationDemoState();
}
class _CombinedAnimationDemoState extends State<CombinedAnimationDemo> with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnim; // 缩放动画
late Animation<double> _rotateAnim; // 旋转动画
late Animation<double> _opacityAnim; // 透明度动画
@override
void initState() {
super.initState();
// 总时长5秒的控制器
_controller = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
);
// 1. 缩放动画:0.5→1.5(全程5秒)
_scaleAnim = Tween<double>(begin: 0.5, end: 1.5).animate(
CurvedAnimation(parent: _controller, curve: Curves.linear),
);
// 2. 旋转动画:0→360度(延迟1秒开始,持续3秒)
_rotateAnim = Tween<double>(begin: 0, end: 2 * 3.1415).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.2, 0.8), // 0.2*5=1秒开始,0.8*5=4秒结束
),
);
// 3. 透明度动画:0→1(前2秒),1→0(后3秒)
_opacityAnim = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.4), // 0→2秒:0→1
),
);
// 反向透明度(2→5秒:1→0)
_opacityAnim = Tween<double>(begin: 1, end: 0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.4, 1.0),
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('组合动画演示')),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform(
// 组合缩放+旋转变换
transform: Matrix4.identity()
..scale(_scaleAnim.value)
..rotateZ(_rotateAnim.value),
alignment: Alignment.center,
child: Opacity(
opacity: _opacityAnim.value,
child: Container(
width: 200,
height: 200,
color: Colors.purple,
child: const Center(
child: Text(
'组合动画',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
);
},
),
),
);
}
}
组合动画核心技巧
- 同时播放:多个Animation绑定同一个控制器,同步变化
- 顺序播放:使用
Interval曲线控制每个动画的时间区间(0.0~1.0对应总时长) - 叠加播放:通过Transform的Matrix4组合缩放、旋转、平移
- 并行控制器:多个AnimationController分别控制不同动画,通过监听实现协同
三、动画按钮+卡片入场效果
需求
实现一个「点击按钮触发卡片的组合动画」:
- 按钮点击后,按钮本身缩放+变色
- 卡片从屏幕右侧滑入,同时伴随缩放、渐显、旋转
- 支持反向动画(卡片滑出,按钮恢复)
完整代码
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '进阶动画综合案例',
theme: ThemeData(primarySwatch: Colors.blue),
home: const CombinedAnimationApp(),
);
}
}
class CombinedAnimationApp extends StatefulWidget {
const CombinedAnimationApp({super.key});
@override
State<CombinedAnimationApp> createState() => _CombinedAnimationAppState();
}
class _CombinedAnimationAppState extends State<CombinedAnimationApp> with TickerProviderStateMixin {
late AnimationController _mainController;
// 按钮动画
late Animation<double> _btnScaleAnim;
late Animation<Color?> _btnColorAnim;
// 卡片动画
late Animation<Offset> _cardOffsetAnim;
late Animation<double> _cardScaleAnim;
late Animation<double> _cardOpacityAnim;
late Animation<double> _cardRotateAnim;
bool _isExpanded = false;
@override
void initState() {
super.initState();
// 初始化主控制器(时长800ms)
_mainController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
// 1. 按钮动画配置
_btnScaleAnim = Tween<double>(begin: 1.0, end: 0.9).animate(
CurvedAnimation(parent: _mainController, curve: Curves.easeInOut),
);
_btnColorAnim = ColorTween(begin: Colors.blue, end: Colors.red).animate(
CurvedAnimation(parent: _mainController, curve: Curves.easeInOut),
);
// 2. 卡片动画配置
// 偏移:从右侧屏幕外→屏幕中心
_cardOffsetAnim = Tween<Offset>(
begin: const Offset(2, 0), // 右侧外
end: const Offset(0, 0), // 中心
).animate(CurvedAnimation(parent: _mainController, curve: Curves.easeOut));
// 缩放:0.5→1.0
_cardScaleAnim = Tween<double>(begin: 0.5, end: 1.0).animate(
CurvedAnimation(parent: _mainController, curve: Curves.bounceOut),
);
// 透明度:0→1
_cardOpacityAnim = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _mainController, curve: const Interval(0.2, 1.0)),
);
// 旋转:-15°→0°
_cardRotateAnim = Tween<double>(begin: -0.2618, end: 0.0).animate( // -15°(弧度)
CurvedAnimation(parent: _mainController, curve: Curves.easeInOut),
);
}
// 切换动画状态
void _toggleAnimation() {
setState(() {
_isExpanded = !_isExpanded;
});
if (_isExpanded) {
_mainController.forward(); // 正向播放(显示卡片)
} else {
_mainController.reverse(); // 反向播放(隐藏卡片)
}
}
@override
void dispose() {
_mainController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('进阶动画综合案例')),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 动画按钮
AnimatedBuilder(
animation: _mainController,
builder: (context, child) {
return Transform.scale(
scale: _btnScaleAnim.value,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: _btnColorAnim.value,
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: _toggleAnimation,
child: Text(
_isExpanded ? '收起卡片' : '显示卡片',
style: const TextStyle(fontSize: 18, color: Colors.white),
),
),
);
},
),
const SizedBox(height: 30),
// 动画卡片
Expanded(
child: AnimatedBuilder(
animation: _mainController,
builder: (context, child) {
return SlideTransition(
position: _cardOffsetAnim,
child: Opacity(
opacity: _cardOpacityAnim.value,
child: Transform(
transform: Matrix4.identity()
..scale(_cardScaleAnim.value)
..rotateZ(_cardRotateAnim.value),
alignment: Alignment.center,
child: Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
blurRadius: 10,
offset: const Offset(0, 5),
)
],
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.star, color: Colors.amber, size: 60),
SizedBox(height: 20),
Text(
'组合动画演示',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
Text(
'包含:位移、缩放、旋转、透明度、颜色动画\n基于AnimationController+Tween+AnimatedBuilder实现',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, color: Colors.grey),
),
],
),
),
),
),
);
},
),
),
],
),
),
);
}
}
关键注意事项
- 所有AnimationController必须在dispose中释放,避免内存泄漏
- 避免用
addListener + setState实现动画,优先使用AnimatedBuilder - 组合动画时,通过一个主控制器管理多个Animation,简化状态控制