flutter-animation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFlutter Animations Implementation
Flutter动画实现
Goal
目标
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.
实现并管理Flutter动画,根据UI需求选择合适的动画策略(隐式、显式、补间、物理、Hero或交错动画)。假设已具备可用的Flutter环境、有状态/无状态组件开发能力,以及标准的组件树结构。
Instructions
操作步骤
1. Determine Animation Strategy (Decision Logic)
1. 确定动画策略(决策逻辑)
Evaluate the UI requirement using the following decision tree to select the correct animation approach:
- Is the animation a simple property change (color, size, alignment) on a single widget?
- YES: Use Implicit Animations (e.g., ).
AnimatedContainer - NO: Proceed to 2.
- YES: Use Implicit Animations (e.g.,
- Does the animation model real-world movement (springs, gravity, velocity)?
- YES: Use Physics-based animation (,
SpringSimulation).animateWith - NO: Proceed to 3.
- YES: Use Physics-based animation (
- Does the animation involve a widget flying between two different screens/routes?
- YES: Use Hero Animations (widget).
Hero - NO: Proceed to 4.
- YES: Use Hero Animations (
- Does the animation involve multiple sequential or overlapping movements?
- YES: Use Staggered Animations (Single with multiple
AnimationControllers andTweencurves).Interval - NO: Use Standard Explicit Animations (,
AnimationController,Tween/AnimatedBuilder).AnimatedWidget
- YES: Use Staggered Animations (Single
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.
使用以下决策树评估UI需求,选择正确的动画方案:
- 动画是否为单个组件的简单属性变化(颜色、尺寸、对齐方式)?
- 是:使用隐式动画(例如)。
AnimatedContainer - 否:进入步骤2。
- 是:使用隐式动画(例如
- 动画是否模拟现实世界的运动(弹簧、重力、速度)?
- 是:使用物理驱动动画(、
SpringSimulation)。animateWith - 否:进入步骤3。
- 是:使用物理驱动动画(
- 动画是否涉及组件在两个不同页面/路由间的过渡飞行?
- 是:使用Hero动画(组件)。
Hero - 否:进入步骤4。
- 是:使用Hero动画(
- 动画是否包含多个连续或重叠的运动?
- 是:使用交错动画(单个搭配多个
AnimationController和Tween曲线)。Interval - 否:使用标准显式动画(、
AnimationController、Tween/AnimatedBuilder)。AnimatedWidget
- 是:使用交错动画(单个
停止并询问用户: 如果需求不明确,请暂停并让用户澄清期望的视觉效果,再编写实现代码。
2. Implement Implicit Animations
2. 实现隐式动画
For simple transitions between values, use implicit animation widgets. Do not manually manage state or controllers.
dart
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(),
)对于值之间的简单过渡,使用隐式动画组件,无需手动管理状态或控制器。
dart
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(),
)3. Implement Explicit Animations (Tween-based)
3. 实现显式动画(基于补间)
When you need to control the animation (play, pause, reverse), use an with a . Separate the transition rendering from the state using .
AnimationControllerTweenAnimatedBuilderdart
class _MyAnimatedWidgetState extends State<MyAnimatedWidget> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
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();
}
void dispose() {
_controller.dispose(); // STRICT REQUIREMENT
super.dispose();
}
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
);
}
}当需要控制动画(播放、暂停、倒放)时,使用搭配。使用将过渡渲染与状态分离。
AnimationControllerTweenAnimatedBuilderdart
class _MyAnimatedWidgetState extends State<MyAnimatedWidget> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
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();
}
void dispose() {
_controller.dispose(); // 严格要求
super.dispose();
}
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return SizedBox(
height: _animation.value,
width: _animation.value,
child: child,
);
},
child: const FlutterLogo(), // 作为子组件传递以提升性能
);
}
}4. Implement Page Route Transitions
4. 实现页面路由过渡
To animate transitions between routes, use and chain a with a .
PageRouteBuilderCurveTweenTween<Offset>dart
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,
);
},
);
}要为路由间的过渡添加动画,使用并将与链式结合。
PageRouteBuilderCurveTweenTween<Offset>dart
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,
);
},
);
}5. Implement Physics-Based Animations
5. 实现物理驱动动画
For realistic motion (e.g., snapping back after a drag), calculate velocity and apply a .
SpringSimulationdart
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);
}对于逼真的运动效果(例如拖拽后弹回),计算速度并应用。
SpringSimulationdart
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);
}6. Implement Hero Animations (Shared Element)
6. 实现Hero动画(共享元素)
To fly a widget between routes, wrap the identical widget tree in both routes with a widget using the exact same .
Herotagdart
// 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),
)要让组件在路由间过渡飞行,在两个路由中使用完全相同的,将相同的组件树包裹在组件中。
tagHerodart
// 源路由
Hero(
tag: 'unique-photo-tag',
child: Image.asset('photo.png', width: 100),
)
// 目标路由
Hero(
tag: 'unique-photo-tag',
child: Image.asset('photo.png', width: 300),
)7. Implement Staggered Animations
7. 实现交错动画
For sequential or overlapping animations, use a single and define multiple s with curves.
AnimationControllerTweenIntervaldart
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 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),
);
},
);
}
}对于连续或重叠的动画,使用单个并为多个定义曲线。
AnimationControllerTweenIntervaldart
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 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),
);
},
);
}
}8. Validate-and-Fix Loop
8. 验证与修复循环
After generating animation code, verify the following:
- Does the class use
State(orSingleTickerProviderStateMixinfor multiple controllers)?TickerProviderStateMixin - Is explicitly called in the
_controller.dispose()method?dispose() - If using , is the static widget passed to the
AnimatedBuilderparameter rather than rebuilt inside thechildfunction? If any of these are missing, fix the code immediately before presenting it to the user.builder
生成动画代码后,验证以下内容:
- 类是否使用了
State(多控制器时使用SingleTickerProviderStateMixin)?TickerProviderStateMixin - 是否在方法中显式调用了
dispose()?_controller.dispose() - 如果使用,静态组件是否传递给了
AnimatedBuilder参数,而非在child函数内重建? 如果以上任何一项缺失,在将代码呈现给用户前立即修复。builder
Constraints
约束条件
- Strict Disposal: You MUST include methods for all
dispose()instances to prevent memory leaks.AnimationController - No URLs: Do not include external links or URLs in the output or comments.
- Immutability: Treat and
Tweenclasses as stateless and immutable. Do not attempt to mutate them after instantiation.Curve - Performance: Always use or
AnimatedBuilderinstead of callingAnimatedWidgetinside a controller'ssetState()when building complex widget trees.addListener - Hero Tags: Hero tags MUST be identical and unique per route transition. Do not use generic tags like if multiple heroes exist.
'image'
- 严格销毁: 必须为所有实例添加
AnimationController方法,以防止内存泄漏。dispose() - 禁止URL: 输出或注释中不得包含外部链接或URL。
- 不可变性: 将和
Tween类视为无状态且不可变的,实例化后不要尝试修改它们。Curve - 性能优化: 构建复杂组件树时,始终使用或
AnimatedBuilder,而非在控制器的AnimatedWidget中调用addListener。setState() - Hero标签: Hero标签必须完全相同,且每个路由过渡的标签唯一。如果存在多个Hero组件,不要使用类似的通用标签。
'image'