动画
动画让 App 更有活力。Flutter 提供了从简单到复杂的多种动画方案。
动画分类
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 隐式动画 | 简单,自动管理 | 简单的属性变化动画 |
| 显式动画 | 灵活,手动控制 | 复杂的自定义动画 |
| Hero 动画 | 页面间共享元素 | 页面跳转时的过渡动画 |
一、隐式动画
隐式动画是最简单的动画方式,你只需要设置目标值,框架自动处理动画过程。
AnimatedContainer
dart
class AnimatedBox extends StatefulWidget {
@override
State<AnimatedBox> createState() => _AnimatedBoxState();
}
class _AnimatedBoxState extends State<AnimatedBox> {
bool _expanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _expanded = !_expanded),
// ─── ★ AnimatedContainer 隐式动画 ──────────────
child: AnimatedContainer(
// ─── ★ 动画时长 ──────────────
duration: Duration(milliseconds: 300),
// ─── ☆ 动画时长 ──────────────
// ─── ★ 动画曲线 ──────────────
curve: Curves.easeInOut,
// ─── ☆ 动画曲线 ──────────────
width: _expanded ? 200 : 100,
height: _expanded ? 200 : 100,
color: _expanded ? Colors.blue : Colors.red,
alignment: Alignment.center,
child: Text('点击切换'),
),
// ─── ☆ AnimatedContainer 隐式动画 ──────────────
);
}
}AnimatedOpacity — 渐入渐出
dart
import 'package:flutter/material.dart';
class AnimatedOpacityExample extends StatefulWidget {
const AnimatedOpacityExample({super.key});
@override
State<AnimatedOpacityExample> createState() => _AnimatedOpacityExampleState();
}
class _AnimatedOpacityExampleState extends State<AnimatedOpacityExample> {
bool _visible = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AnimatedOpacity')),
body: Center(
// ─── ★ AnimatedOpacity 渐入渐出 ──────────────
child: AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: const Text('渐入渐出', style: TextStyle(fontSize: 24)),
),
// ─── ☆ AnimatedOpacity 渐入渐出 ──────────────
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _visible = !_visible),
child: const Icon(Icons.visibility),
),
);
}
}AnimatedSwitcher — 切换动画
dart
import 'package:flutter/material.dart';
class AnimatedSwitcherExample extends StatefulWidget {
const AnimatedSwitcherExample({super.key});
@override
State<AnimatedSwitcherExample> createState() => _AnimatedSwitcherExampleState();
}
class _AnimatedSwitcherExampleState extends State<AnimatedSwitcherExample> {
bool _showA = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AnimatedSwitcher')),
body: Center(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: _showA
? const Text('A', key: ValueKey('a'), style: TextStyle(fontSize: 48))
: const Text('B', key: ValueKey('b'), style: TextStyle(fontSize: 48)),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _showA = !_showA),
child: const Icon(Icons.swap_horiz),
),
);
}
}其他隐式动画组件
| 组件 | 动画属性 |
|---|---|
AnimatedContainer | 宽、高、颜色、边距等 |
AnimatedOpacity | 不透明度 |
AnimatedSwitcher | 子组件切换 |
AnimatedPadding | 内边距 |
AnimatedAlign | 对齐方式 |
AnimatedPositioned | 位置(需在 Stack 中) |
AnimatedDefaultTextStyle | 文字样式 |
AnimatedIcon | 图标动画 |
AnimatedCrossFade | 两个子组件交叉渐变 |
隐式动画常用属性
| 属性 | 说明 |
|---|---|
duration | 动画时长 |
curve | 动画曲线(缓动函数) |
onEnd | 动画结束回调 |
常用 Curves 缓动曲线
dart
Curves.linear // 匀速
Curves.easeIn // 慢启动
Curves.easeOut // 慢结束
Curves.easeInOut // 慢启动慢结束
Curves.bounceIn // 弹入
Curves.bounceOut // 弹出
Curves.elasticIn // 弹性入
Curves.elasticOut // 弹性出
Curves.fastOutSlowIn // Material 标准曲线二、显式动画
显式动画使用 AnimationController 手动控制动画过程,灵活度更高。
AnimationController
dart
class FadeInWidget extends StatefulWidget {
final Widget child;
const FadeInWidget({super.key, required this.child});
@override
State<FadeInWidget> createState() => _FadeInWidgetState();
}
class _FadeInWidgetState extends State<FadeInWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
);
_controller.forward(); // 启动动画
}
@override
void dispose() {
_controller.dispose(); // 必须释放
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: widget.child,
);
}
}AnimationController 常用方法
| 方法 | 说明 |
|---|---|
forward() | 从头到尾播放 |
reverse() | 从尾到头播放 |
repeat() | 循环播放 |
stop() | 停止 |
reset() | 重置到起点 |
animateTo(target) | 动画到指定值 |
animateBack(target) | 反向动画到指定值 |
Tween — 值范围
dart
// 数值范围
final tween = Tween<double>(begin: 0, end: 1);
final value = tween.transform(_controller.value);
// 颜色范围
final colorTween = ColorTween(begin: Colors.red, end: Colors.blue);
final color = colorTween.transform(_controller.value);
// 使用 animate 方法
final animation = Tween<double>(begin: 0, end: 200).animate(_controller);Transition 组件
| 组件 | 动画效果 |
|---|---|
FadeTransition | 渐变透明度 |
SlideTransition | 滑动位置 |
ScaleTransition | 缩放 |
RotationTransition | 旋转 |
SizeTransition | 尺寸 |
PositionedTransition | 位置变化 |
常见动画模式
渐变 + 位移:
dart
SlideTransition(
position: Tween<Offset>(
begin: Offset(0, 0.5), // 从下方开始
end: Offset.zero,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)),
child: FadeTransition(
opacity: _controller,
child: widget.child,
),
)交错动画(Staggered Animation):
dart
// 多个动画按时间顺序执行
final fadeIn = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 0.5), // 前半段时间
),
);
final slideUp = Tween<Offset>(begin: Offset(0, 0.5), end: Offset.zero).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.3, 1.0), // 后 70% 时间
),
);三、Hero — 共享元素动画
Hero 动画让一个元素在页面跳转时"飞"过去,非常适合图片详情页等场景。
基本用法
dart
import 'package:flutter/material.dart';
// 页面 A:列表页
class HeroListPage extends StatelessWidget {
const HeroListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('相册')),
body: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const HeroDetailPage()),
);
},
child: Hero(
tag: 'photo_123', // 唯一标识
child: Container(
width: 100, height: 100,
color: Colors.blue,
child: const Icon(Icons.image, color: Colors.white, size: 48),
),
),
),
);
}
}
// 页面 B:详情页
class HeroDetailPage extends StatelessWidget {
const HeroDetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('详情')),
body: Center(
child: Hero(
tag: 'photo_123', // 相同的 tag
child: Container(
width: 200, height: 200,
color: Colors.blue,
child: const Icon(Icons.image, color: Colors.white, size: 96),
),
),
),
);
}
}完整示例
dart
// 列表页
class PhotoListPage extends StatelessWidget {
final List<String> photos = ['photo1', 'photo2', 'photo3'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('相册')),
body: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: photos.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoDetailPage(photoId: photos[index]),
),
),
child: Hero(
tag: 'photo_${photos[index]}',
child: Image.asset('assets/${photos[index]}.jpg', fit: BoxFit.cover),
),
);
},
),
);
}
}
// 详情页
class PhotoDetailPage extends StatelessWidget {
final String photoId;
const PhotoDetailPage({super.key, required this.photoId});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Hero(
tag: 'photo_$photoId',
child: Image.asset('assets/$photoId.jpg'),
),
),
);
}
}Hero 动画自定义
dart
Hero(
tag: 'photo_123',
flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
return ScaleTransition(
scale: animation,
child: Image.asset('assets/photo.jpg'),
);
},
child: Image.asset('assets/photo.jpg'),
)动画选择指南
| 需求 | 方案 |
|---|---|
| 简单属性变化(大小、颜色、透明度) | 隐式动画(AnimatedContainer 等) |
| 需要精确控制动画过程 | 显式动画(AnimationController) |
| 页面间元素过渡 | Hero 动画 |
| 复杂的多步动画 | 交错动画(Interval) |
| 循环动画 | AnimationController.repeat() |
建议
- 优先使用隐式动画——代码少,不易出错
- 需要精确控制时才用显式动画
- 动画时长一般 200~500ms,太短看不清,太长感觉卡
- 使用
Curves.easeInOut等曲线让动画更自然
