Marketplace flutter-animations
Comprehensive guide for implementing animations in Flutter. Use when adding motion and visual effects to Flutter apps: implicit animations (AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder), explicit animations (AnimationController, Tween, AnimatedWidget/AnimatedBuilder), hero animations (shared element transitions), staggered animations (sequential/overlapping), and physics-based animations. Includes workflow for choosing the right animation type, implementation patterns, and best practices for performance and user experience.
git clone https://github.com/aiskillstore/marketplace
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/madteacher/flutter-animations" ~/.claude/skills/aiskillstore-marketplace-flutter-animations && rm -rf "$T"
skills/madteacher/flutter-animations/SKILL.mdFlutter Animations
Overview
Create smooth, performant animations in Flutter using the right approach for each use case. This skill covers complete animation workflow: from choosing between implicit/explicit approaches to implementing complex effects like hero transitions and staggered animations.
Animation Type Decision Tree
Choose the right animation type based on your requirements:
Implicit Animations - Use when:
- Animating a single property (color, size, position)
- Animation is triggered by state change
- No need for fine-grained control
Explicit Animations - Use when:
- Need full control over animation lifecycle
- Animating multiple properties simultaneously
- Need to react to animation state changes
- Creating custom animations or transitions
Hero Animations - Use when:
- Sharing an element between two screens
- Creating shared element transitions
- User expects element to "fly" between routes
Staggered Animations - Use when:
- Multiple animations should run sequentially or overlap
- Creating ripple effects or sequential reveals
- Animating list items in sequence
Physics-Based Animations - Use when:
- Animations should feel natural/physical
- Spring-like behavior, scrolling gestures
- Draggable interactions
Implicit Animations
Implicit animations automatically handle the animation when properties change. No controller needed.
Common Implicit Widgets
AnimatedContainer - Animates multiple properties (size, color, decoration, padding):
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 - Simple fade animation:
AnimatedOpacity( opacity: _visible ? 1.0 : 0.0, duration: const Duration(milliseconds: 300), child: const Text('Hello'), )
TweenAnimationBuilder - Custom tween animation without boilerplate:
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(), )
Other implicit widgets:
- Padding animationAnimatedPadding
- Position animation (in Stack)AnimatedPositioned
- Alignment animationAnimatedAlign
- Multiple propertiesAnimatedContainer
- Cross-fade between widgetsAnimatedSwitcher
- Text style animationAnimatedDefaultTextStyle
Best Practices
- Prefer implicit animations for simple cases
- Use appropriate curves for natural motion (see
class)Curves - Set
andcurve
for predictable behaviorduration - Use
callback when neededonEnd - Avoid nested implicit animations for performance
Explicit Animations
Explicit animations provide full control with AnimationController.
Core Components
AnimationController - Drives the animation:
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 - Interpolates between begin and end values:
animation = Tween<double>(begin: 0, end: 300).animate(_controller);
CurvedAnimation - Applies a curve to the animation:
animation = CurvedAnimation( parent: _controller, curve: Curves.easeInOut, );
AnimatedWidget Pattern
Best for reusable animated widgets:
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 Pattern
Best for complex widgets with animations:
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, ), ); } }
Monitoring Animation State
animation.addStatusListener((status) { switch (status) { case AnimationStatus.completed: _controller.reverse(); break; case AnimationStatus.dismissed: _controller.forward(); break; default: break; } });
Multiple Simultaneous Animations
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(), ), ), ); } }
Built-in Explicit Transitions
Flutter provides ready-to-use transitions:
- Fade animationFadeTransition
- Scale animationScaleTransition
- Slide animationSlideTransition
- Size animationSizeTransition
- Rotation animationRotationTransition
- Position animation (in Stack)PositionedTransition
Example:
FadeTransition( opacity: _animation, child: const FlutterLogo(), )
Performance Tips
- Dispose controllers when widget is removed
- Use
for optimal rebuildsAnimatedBuilder - Avoid
in animation listeners (usesetState()
/AnimatedWidget
)AnimatedBuilder - Use
to slow animations during debuggingtimeDilation
Hero Animations
Hero animations create shared element transitions between screens.
Basic Hero Animation
Source screen:
Hero( tag: 'hero-image', child: Image.asset('images/logo.png'), )
Destination screen:
Hero( tag: 'hero-image', // Same tag! child: Image.asset('images/logo.png'), )
Complete Example
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), ), ), ), ); } }
Navigating between screens:
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(), ), ), ); }, ), );
Radial Hero Animation
Transform from circle to rectangle during transition:
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), ), ), ); } }
Use with
MaterialRectCenterArcTween for center-based interpolation:
static RectTween _createRectTween(Rect? begin, Rect? end) { return MaterialRectCenterArcTween(begin: begin, end: end); }
Hero Best Practices
- Use unique, consistent tags (often the data object itself)
- Keep hero widget trees similar between routes
- Wrap images in
with transparent color for "pop" effectMaterial - Use
to debug transitionstimeDilation - Consider
to disable hero animations when neededHeroMode
Staggered Animations
Run multiple animations with different timing.
Basic Staggered Animation
All animations share one controller:
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-Based Timing
Each animation has an Interval between 0.0 and 1.0:
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, ), ), );
Common Tweens
borderRadius = BorderRadiusTween( begin: BorderRadius.circular(4), end: BorderRadius.circular(75), ).animate( CurvedAnimation( parent: controller, curve: const Interval(0.375, 0.500, curve: Curves.ease), ), );
Staggered Menu Animation
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(); } }
Stagger Best Practices
- Use
to offset animations in timeInterval - Ensure controller duration covers all intervals
- Use curves for natural motion within intervals
- Consider
to debug timingtimeDilation - Stagger menu items with increasing delay for ripple effect
Physics-Based Animations
Create natural-feeling animations using physics simulations.
Fling Animation
_controller.fling( velocity: 2.0, // Units per second );
Custom Physics Simulation
_controller.animateWith( SpringSimulation( spring: const SpringDescription( mass: 1, stiffness: 100, damping: 10, ), start: 0.0, end: 1.0, velocity: 0.0, ), );
Common Physics Simulations
- Spring physicsSpringSimulation
- Scroll with bounceBouncingScrollSimulation
- Scroll without bounceClampingScrollSimulation
- Gravity-basedGravitySimulation
Best Practices
DO
- Dispose AnimationController in widget disposal
- Use
/AnimatedBuilder
instead ofAnimatedWidget
in listenerssetState() - Choose appropriate curves for natural motion
- Use
for debugging animationstimeDilation - Consider performance (avoid heavy widgets in animation builds)
- Test animations on various devices
- Support reverse animations for intuitive feel
DON'T
- Forget to dispose AnimationController (memory leak)
- Use
in animation listeners whensetState()
sufficesAnimatedBuilder - Assume animation completes instantly (handle
)AnimationStatus - Over-animate (animations can distract users)
- Create animations that feel "jerky" (use smooth curves)
- Ignore accessibility (respect
preference)disableAnimations
Resources
references/
implicit.md - Complete reference for implicit animation widgets with examples and best practices.
explicit.md - Deep dive into explicit animations, AnimationController, and patterns.
hero.md - Hero animations guide with standard and radial transitions.
staggered.md - Staggered animation patterns and timing strategies.
physics.md - Physics-based animations and simulations.
curves.md - Reference for Curves class and choosing appropriate curves.
assets/templates/
Template code for common animation patterns:
- Implicit animation examplesimplicit_animation.dart
- Explicit animation setupexplicit_animation.dart
- Hero animation boilerplatehero_transition.dart
- Staggered animation templatestaggered_animation.dart