🚀 快速安装
复制以下命令并运行,立即安装此 Skill:
npx @anthropic-ai/skills install madteacher/mad-agents-skills/flutter-animations
💡 提示:需要 Node.js 和 NPM
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('你好'),
)
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;
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
}
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);
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;
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);
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', // 相同的标签!
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;
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('详情')),
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;
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 组件树相似
- 将图片包装在具有透明颜色的
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,
),
),
);
}
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: _buildAnimation,
);
}
}
基于区间的时序
每个动画在 0.0 到 1.0 之间有一个区间:
animation = Tween<double>(begin: 0, end: 300).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.25, // 在控制器持续时间的 25% 时开始
0.50, // 在控制器持续时间的 50% 时结束
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;
void initState() {
super.initState();
_controller = AnimationController(
duration: _animationDuration,
vsync: this,
);
_controller.forward();
}
void dispose() {
_controller.dispose();
super.dispose();
}
}
交错最佳实践
- 使用
Interval在时间上偏移动画 - 确保控制器持续时间覆盖所有区间
- 在区间内使用曲线实现自然运动
- 考虑使用
timeDilation调试时序 - 对于菜单项,使用递增延迟实现涟漪效果
基于物理的动画
使用物理模拟创建自然感觉的动画。
Fling 动画
_controller.fling(
velocity: 2.0, // 单位每秒
);
自定义物理模拟
_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– 基于重力的模拟
最佳实践
要做的
- 在组件销毁时释放 AnimationController
- 使用
AnimatedBuilder/AnimatedWidget而不是在监听器中使用setState() - 选择适当的曲线以实现自然运动
- 使用
timeDilation调试动画 - 考虑性能(避免在动画构建中使用重型组件)
- 在各种设备上测试动画
- 支持反向动画以实现直观感受
不要做的
- 忘记释放 AnimationController(会导致内存泄漏)
- 在
AnimatedBuilder足够的情况下,在动画监听器中使用setState() - 假设动画会立即完成(需处理
AnimationStatus) - 过度动画化(动画可能会分散用户注意力)
- 创建感觉“卡顿”的动画(使用平滑的曲线)
- 忽略可访问性(尊重
disableAnimations偏好)
资源
references/
implicit.md – 包含示例和最佳实践的隐式动画组件完整参考。
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– 交错动画模板
📄 原始文档
完整文档(英文):
https://skills.sh/madteacher/mad-agents-skills/flutter-animations
💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

评论(0)