flutter-animation
Flutter Animations Implementation
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.
Instructions
1. Determine Animation Strategy (Decision Logic)
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 (
Herowidget). - NO: Proceed to 4.
- YES: Use Hero Animations (
- Does the animation involve multiple sequential or overlapping movements?
- YES: Use Staggered Animations (Single
AnimationControllerwith multipleTweens andIntervalcurves). - 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.
2. Implement Implicit Animations
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(),
)
3. Implement Explicit Animations (Tween-based)
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;
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
);
}
}
4. Implement Page Route Transitions
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,
);
},
);
}
5. Implement Physics-Based Animations
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);
}
6. Implement Hero Animations (Shared Element)
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),
)
7. Implement Staggered Animations
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;
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
After generating animation code, verify the following:
- Does the
Stateclass useSingleTickerProviderStateMixin(orTickerProviderStateMixinfor multiple controllers)? - Is
_controller.dispose()explicitly called in thedispose()method? - If using
AnimatedBuilder, is the static widget passed to thechildparameter rather than rebuilt inside thebuilderfunction? If any of these are missing, fix the code immediately before presenting it to the user.
Constraints
- Strict Disposal: You MUST include
dispose()methods for allAnimationControllerinstances to prevent memory leaks. - No URLs: Do not include external links or URLs in the output or comments.
- Immutability: Treat
TweenandCurveclasses as stateless and immutable. Do not attempt to mutate them after instantiation. - Performance: Always use
AnimatedBuilderorAnimatedWidgetinstead of callingsetState()inside a controller'saddListenerwhen building complex widget trees. - Hero Tags: Hero tags MUST be identical and unique per route transition. Do not use generic tags like
'image'if multiple heroes exist.