flutter-animation by flutter/skills
npx skills add https://github.com/flutter/skills --skill flutter-animation实现和管理 Flutter 动画,根据 UI 需求选择合适的动画策略(隐式、显式、补间、物理、Hero 或交错动画)。假设已具备可运行的 Flutter 环境、有状态/无状态组件能力以及标准的组件树结构。
使用以下决策树评估 UI 需求,以选择正确的动画方法:
AnimatedContainer)。SpringSimulation、animateWith)。Hero 组件)。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
AnimationController 配合多个 Tween 和 Interval 曲线)。AnimationController、Tween、AnimatedBuilder / AnimatedWidget)。停止并询问用户: 如果需求不明确,请在编写实现代码之前暂停,并请用户澄清期望的视觉效果。
对于值之间的简单过渡,使用隐式动画组件。不要手动管理状态或控制器。
AnimatedContainer(
duration: const Duration(milliseconds: 500),
curve: Curves.bounceIn,
width: _isExpanded ? 200.0 : 100.0,
height: _isExpanded ? 200.0 : 100.0,
decoration: BoxDecoration(
color: _isExpanded ? Colors.green : Colors.blue,
borderRadius: BorderRadius.circular(_isExpanded ? 50.0 : 8.0),
),
child: const FlutterLogo(),
)
当需要控制动画(播放、暂停、反转)时,使用 AnimationController 配合 Tween。使用 AnimatedBuilder 将过渡渲染与状态分离。
class _MyAnimatedWidgetState extends State<MyAnimatedWidget> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 300).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
)..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 AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return SizedBox(
height: _animation.value,
width: _animation.value,
child: child,
);
},
child: const FlutterLogo(), // 作为子组件传入以提高性能
);
}
}
要为路由之间的过渡添加动画,请使用 PageRouteBuilder,并用 CurveTween 和 Tween<Offset> 进行链式调用。
Route<void> _createRoute() {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => const DestinationPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.ease;
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
);
}
对于逼真的运动(例如,拖动后回弹),计算速度并应用 SpringSimulation。
void _runSpringAnimation(Offset pixelsPerSecond, Size size, Alignment dragAlignment) {
_animation = _controller.drive(
AlignmentTween(begin: dragAlignment, end: Alignment.center),
);
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(mass: 1, stiffness: 1, damping: 1);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
要使组件在路由之间飞行,请使用完全相同的 tag,在两个路由中用 Hero 组件包装相同的组件树。
// 源路由
Hero(
tag: 'unique-photo-tag',
child: Image.asset('photo.png', width: 100),
)
// 目标路由
Hero(
tag: 'unique-photo-tag',
child: Image.asset('photo.png', width: 300),
)
对于顺序或重叠的动画,使用单个 AnimationController,并使用 Interval 曲线定义多个 Tween。
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;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Opacity(
opacity: opacity.value,
child: Container(width: width.value, height: 50, color: Colors.blue),
);
},
);
}
}
生成动画代码后,请验证以下内容:
State 类是否使用了 SingleTickerProviderStateMixin(或用于多个控制器的 TickerProviderStateMixin)?dispose() 方法中是否明确调用了 _controller.dispose()?AnimatedBuilder,静态组件是否传递给了 child 参数,而不是在 builder 函数内部重建?如果缺少任何一项,请在将代码呈现给用户之前立即修复。AnimationController 实例包含 dispose() 方法,以防止内存泄漏。Tween 和 Curve 类视为无状态且不可变的。实例化后不要尝试修改它们。AnimatedBuilder 或 AnimatedWidget,而不是在控制器的 addListener 内部调用 setState()。'image'。每周安装量
1.1K
代码库
GitHub 星标数
784
首次出现
2026年3月4日
安全审计
安装于
codex1.1K
cursor1.1K
opencode1.1K
gemini-cli1.1K
github-copilot1.1K
kimi-cli1.1K
Implements and manages Flutter animations, selecting the appropriate animation strategy (implicit, explicit, tween, physics, hero, or staggered) based on UI requirements. Assumes a working Flutter environment, stateful/stateless widget competence, and a standard widget tree structure.
Evaluate the UI requirement using the following decision tree to select the correct animation approach:
AnimatedContainer).SpringSimulation, animateWith).Hero widget).AnimationController with multiple Tweens and Interval curves).AnimationController, Tween, AnimatedBuilder / AnimatedWidget).STOP AND ASK THE USER: If the requirement is ambiguous, pause and ask the user to clarify the desired visual effect before writing implementation code.
For simple transitions between values, use implicit animation widgets. Do not manually manage state or controllers.
AnimatedContainer(
duration: const Duration(milliseconds: 500),
curve: Curves.bounceIn,
width: _isExpanded ? 200.0 : 100.0,
height: _isExpanded ? 200.0 : 100.0,
decoration: BoxDecoration(
color: _isExpanded ? Colors.green : Colors.blue,
borderRadius: BorderRadius.circular(_isExpanded ? 50.0 : 8.0),
),
child: const FlutterLogo(),
)
When you need to control the animation (play, pause, reverse), use an AnimationController with a Tween. Separate the transition rendering from the state using AnimatedBuilder.
class _MyAnimatedWidgetState extends State<MyAnimatedWidget> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 300).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
)..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
@override
void dispose() {
_controller.dispose(); // STRICT REQUIREMENT
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return SizedBox(
height: _animation.value,
width: _animation.value,
child: child,
);
},
child: const FlutterLogo(), // Passed as child for performance
);
}
}
To animate transitions between routes, use PageRouteBuilder and chain a CurveTween with a Tween<Offset>.
Route<void> _createRoute() {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => const DestinationPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.ease;
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
);
}
For realistic motion (e.g., snapping back after a drag), calculate velocity and apply a SpringSimulation.
void _runSpringAnimation(Offset pixelsPerSecond, Size size, Alignment dragAlignment) {
_animation = _controller.drive(
AlignmentTween(begin: dragAlignment, end: Alignment.center),
);
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(mass: 1, stiffness: 1, damping: 1);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
To fly a widget between routes, wrap the identical widget tree in both routes with a Hero widget using the exact same tag.
// Source Route
Hero(
tag: 'unique-photo-tag',
child: Image.asset('photo.png', width: 100),
)
// Destination Route
Hero(
tag: 'unique-photo-tag',
child: Image.asset('photo.png', width: 300),
)
For sequential or overlapping animations, use a single AnimationController and define multiple Tweens with Interval curves.
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;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Opacity(
opacity: opacity.value,
child: Container(width: width.value, height: 50, color: Colors.blue),
);
},
);
}
}
After generating animation code, verify the following:
State class use SingleTickerProviderStateMixin (or TickerProviderStateMixin for multiple controllers)?_controller.dispose() explicitly called in the dispose() method?AnimatedBuilder, is the static widget passed to the child parameter rather than rebuilt inside the builder function? If any of these are missing, fix the code immediately before presenting it to the user.dispose() methods for all AnimationController instances to prevent memory leaks.Tween and Curve classes as stateless and immutable. Do not attempt to mutate them after instantiation.AnimatedBuilder or AnimatedWidget instead of calling setState() inside a controller's addListener when building complex widget trees.Weekly Installs
1.1K
Repository
GitHub Stars
784
First Seen
Mar 4, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex1.1K
cursor1.1K
opencode1.1K
gemini-cli1.1K
github-copilot1.1K
kimi-cli1.1K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
'image'