关于 flutter-animations
flutter-animations 是面向 Flutter 开发者的动画技能,帮助按场景选择动画方案并实现常见效果。内容涵盖从隐式动画到物理模拟的流程、示例和最佳实践。
主要功能
- 01选择合适的动画类型
- 02实现隐式与显式动画
- 03制作 Hero 页面转场
- 04编排交错动画
- 05创建基于物理的动画
- 06查看示例与最佳实践
适用场景
- 01为状态变化添加属性动画
- 02在页面间共享元素转场
- 03按顺序显示列表或菜单项
- 04实现弹簧或拖拽交互效果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ im5tu/ claude --skill flutter-animations安装后在 Claude Code 中输入/flutter-animations确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/templates/explicit_animation.darthero_transition.dartimplicit_animation.dartstaggered_animation.dartreferences/curves.mdexplicit.mdhero.mdimplicit.mdphysics.mdstaggered.md详细介绍
Flutter 动画
概述
在 Flutter 中,针对不同用例采用合适的方法,创建流畅且高性能的动画。本技能涵盖完整的动画工作流:从选择隐式或显式动画,到实现 Hero 转场和交错动画等复杂效果。
动画类型决策树
根据需求选择合适的动画类型:
隐式动画——适用于:
- 为单个属性设置动画(颜色、大小、位置)
- 动画由状态变化触发
- 不需要精细控制
显式动画——适用于:
- 需要全面控制动画生命周期
- 同时为多个属性设置动画
- 需要响应动画状态变化
- 创建自定义动画或转场
Hero 动画——适用于:
- 在两个屏幕间共享一个元素
- 创建共享元素转场
- 用户期望元素在路由之间“飞动”
交错动画——适用于:
- 多个动画按顺序运行或彼此重叠
- 创建涟漪效果或按顺序显示内容
- 按顺序为列表项设置动画
基于物理效果的动画——适用于:
- 动画应具有自然的物理效果
- 弹簧效果、滚动手势
- 可拖动交互
隐式动画
隐式动画会在属性变化时自动处理动画,无需控制器。
常见的隐式动画组件
AnimatedContainer——为多个属性设置动画(大小、颜色、装饰、内边距):
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 200 : 100,
height: _expanded ? 200 : 100,
color: _expanded ? Colors.blue : Colors.red,
child: const FlutterLogo(),
)
AnimatedOpacity——简单的淡入淡出动画:
AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: const Text('Hello'),
)
TweenAnimationBuilder——无需样板代码即可创建自定义补间动画:
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 1),
duration: const Duration(seconds: 1),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.scale(
scale: value,
child: child,
),
);
},
child: const FlutterLogo(),
)
其他隐式动画组件:
AnimatedPadding——内边距动画AnimatedPositioned——位置动画(在 Stack 中)AnimatedAlign——对齐动画AnimatedContainer——多个属性AnimatedSwitcher——组件间交叉淡入淡出AnimatedDefaultTextStyle——文本样式动画
最佳实践
- 简单场景优先使用隐式动画
- 使用合适的曲线实现自然运动(参见
Curves类) - 设置
curve和duration,确保行为可预测 - 需要时使用
onEnd回调 - 避免嵌套隐式动画,以提升性能
显式动画
显式动画通过 AnimationController 提供全面控制。
核心组件
AnimationController——驱动动画:
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Tween——在起始值和结束值之间进行插值:
animation = Tween<double>(begin: 0, end: 300).animate(_controller);
CurvedAnimation——为动画应用曲线:
animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
AnimatedWidget 模式
最适合可复用的动画组件:
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({super.key, required Animation<double> animation})
: super(listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
}
AnimatedBuilder 模式
最适合带动画的复杂组件:
class GrowTransition extends StatelessWidget {
const GrowTransition({
required this.child,
required this.animation,
super.key,
});
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return SizedBox(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,
),
);
}
}
监控动画状态
animation.addStatusListener((status) {
switch (status) {
case AnimationStatus.completed:
_controller.reverse();
break;
case AnimationStatus.dismissed:
_controller.forward();
break;
default:
break;
}
});
多个同步动画
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({super.key, required Animation<double> animation})
: super(listenable: animation);
static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
static final _sizeTween = Tween<double>(begin: 0, end: 300);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
child: const FlutterLogo(),
),
),
);
}
}
内置显式转场
Flutter 提供可直接使用的转场:
FadeTransition——淡入淡出动画ScaleTransition——缩放动画SlideTransition——滑动动画SizeTransition——尺寸动画RotationTransition——旋转动画PositionedTransition——位置动画(在 Stack 中)
示例:
FadeTransition(
opacity: _animation,
child: const FlutterLogo(),
)
性能提示
- 移除组件时释放控制器
- 使用
AnimatedBuilder优化重建 - 避免在动画监听器中调用
setState()(使用AnimatedWidget/AnimatedBuilder) - 调试时使用
timeDilation减慢动画
Hero 动画
Hero 动画用于创建屏幕之间的共享元素转场。
基础 Hero 动画
源屏幕:
Hero(
tag: 'hero-image',
child: Image.asset('images/logo.png'),
)
目标屏幕:
Hero(
tag: 'hero-image', // Same tag!
child: Image.asset('images/logo.png'),
)
完整示例
class PhotoHero extends StatelessWidget {
const PhotoHero({
super.key,
required this.photo,
this.onTap,
required this.width,
});
final String photo;
final VoidCallback? onTap;
final double width;
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(
tag: photo,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Image.asset(photo, fit: BoxFit.contain),
),
),
),
);
}
}
在屏幕之间导航:
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) {
return Scaffold(
appBar: AppBar(title: const Text('Detail')),
body: Center(
child: PhotoHero(
photo: 'images/logo.png',
width: 300.0,
onTap: () => Navigator.of(context).pop(),
),
),
);
},
),
);
径向 Hero 动画
在转场过程中将圆形变为矩形:
class RadialExpansion extends StatelessWidget {
const RadialExpansion({
super.key,
required this.maxRadius,
this.child,
}) : clipRectSize = 2.0 * (maxRadius / math.sqrt2);
final double maxRadius;
final double clipRectSize;
final Widget? child;
@override
Widget build(BuildContext context) {
return ClipOval(
child: Center(
child: SizedBox(
width: clipRectSize,
height: clipRectSize,
child: ClipRect(child: child),
),
),
);
}
}
搭配 MaterialRectCenterArcTween 使用,以进行基于中心点的插值:
static RectTween _createRectTween(Rect? begin, Rect? end) {
return MaterialRectCenterArcTween(begin: begin, end: end);
}
Hero 最佳实践
- 使用唯一且一致的标签(通常是数据对象本身)
- 让不同路由中的 Hero Widget 树保持相似
- 将图像包裹在颜色透明的
Material中,以营造“突出”效果 - 使用
timeDilation调试过渡效果 - 需要时,考虑使用
HeroMode禁用 Hero 动画
交错动画
以不同的时间安排运行多个动画。
基本交错动画
所有动画共用一个控制器:
class StaggerAnimation extends StatelessWidget {
StaggerAnimation({super.key, required this.controller})
: opacity = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(0.0, 0.100, curve: Curves.ease),
),
),
width = Tween<double>(begin: 50.0, end: 150.0).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(0.125, 0.250, curve: Curves.ease),
),
);
final AnimationController controller;
final Animation<double> opacity;
final Animation<double> width;
Widget _buildAnimation(BuildContext context, Widget? child) {
return Container(
alignment: Alignment.bottomCenter,
child: Opacity(
opacity: opacity.value,
child: Container(
width: width.value,
height: 150,
color: Colors.blue,
),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: _buildAnimation,
);
}
}
基于 Interval 的时间安排
每个动画都有一个介于 0.0 和 1.0 之间的 Interval:
animation = Tween<double>(begin: 0, end: 300).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.25, // Start at 25% of controller duration
0.50, // End at 50% of controller duration
curve: Curves.ease,
),
),
);
常见的补间动画
borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(4),
end: BorderRadius.circular(75),
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(0.375, 0.500, curve: Curves.ease),
),
);
交错菜单动画
class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
static const _initialDelayTime = Duration(milliseconds: 50);
static const _itemSlideTime = Duration(milliseconds: 250);
static const _staggerTime = Duration(milliseconds: 50);
static const _buttonDelayTime = Duration(milliseconds: 150);
static const _buttonTime = Duration(milliseconds: 500);
final _animationDuration =
_initialDelayTime +
(_staggerTime * _menuTitles.length) +
_buttonDelayTime +
_buttonTime;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: _animationDuration,
vsync: this,
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
交错动画最佳实践
- 使用
Interval在时间上错开动画 - 确保控制器的持续时间覆盖所有 Interval
- 在各个 Interval 中使用曲线,让运动更自然
- 考虑使用
timeDilation调试时间安排 - 让菜单项的延迟逐渐增加,营造涟漪效果
基于物理的动画
使用物理模拟创建自然流畅的动画。
Fling 动画
_controller.fling(
velocity: 2.0, // Units per second
);
自定义物理模拟
_controller.animateWith(
SpringSimulation(
spring: const SpringDescription(
mass: 1,
stiffness: 100,
damping: 10,
),
start: 0.0,
end: 1.0,
velocity: 0.0,
),
);
常见物理模拟
SpringSimulation- 弹簧物理效果BouncingScrollSimulation- 带弹跳效果的滚动ClampingScrollSimulation- 不带弹跳效果的滚动GravitySimulation- 基于重力的模拟
最佳实践
应该做
- 在 Widget 销毁时释放 AnimationController
- 在监听器中使用
AnimatedBuilder/AnimatedWidget,而不是setState() - 选择合适的曲线,让运动更自然
- 使用
timeDilation调试动画 - 考虑性能(避免在动画构建过程中使用开销大的 Widget)
- 在各种设备上测试动画
- 支持反向动画,让交互感受更直观
不应该做
- 忘记释放 AnimationController(会造成内存泄漏)
- 在
AnimatedBuilder足以满足需求时,仍在动画监听器中使用setState() - 假设动画会立即完成(应处理
AnimationStatus) - 过度使用动画(动画可能会分散用户注意力)
- 创建感觉“生硬”的动画(应使用平滑的曲线)
- 忽略无障碍功能(尊重
disableAnimations偏好设置)
资源
references/
implicit.md - 隐式动画 Widget 的完整参考,包含示例和最佳实践。
explicit.md - 深入介绍显式动画、AnimationController 和相关模式。
hero.md - Hero 动画指南,介绍标准过渡和径向过渡。
staggered.md - 交错动画模式和时间安排策略。
physics.md - 基于物理的动画和模拟。
curves.md - Curves 类参考,以及如何选择合适的曲线。
assets/templates/
常见动画模式的模板代码:
implicit_animation.dart- 隐式动画示例explicit_animation.dart- 显式动画设置hero_transition.dart- Hero 动画样板代码staggered_animation.dart- 交错动画模板