How to Create a Custom Animation in Flutter: A Complete Guide
•11 min read
<div style="text-align: center;">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDMwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPCEtLSBDdXN0b20gQW5pbWF0aW9uIGV4YW1wbGUgLS0+CiAgPHJlY3Qgd2lkdGg9IjMwMCIgaGVpZ2h0PSIyMDAiIGZpbGw9IiNGRkYiIHN0cm9rZT0iIzAwMCIvPgogIDx0ZXh0IHg9IjE1MCIgeT0iMTAwIiBmb250LWZhbW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMjEyMTIxIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5DdXN0b20gQW5pbWF0aW9uPC90ZXh0Pgo8L3N2Zz4=" alt="Custom Animation Example" width="300" />
</div>
Animations are a crucial part of creating engaging user interfaces in Flutter. This guide will walk you through everything from basic animations to complex physics-based effects.
Getting Started with Animations
1. Basic Animation with AnimationController
import 'package:flutter/material.dart'; class BasicAnimation extends StatefulWidget { @override _BasicAnimationState createState() => _BasicAnimationState(); } class _BasicAnimationState extends State<BasicAnimation> 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: 1).animate(_controller) ..addListener(() { setState(() {}); }); _controller.repeat(reverse: true); } @override Widget build(BuildContext context) { return Center( child: Container( width: 100 + (100 * _animation.value), height: 100 + (100 * _animation.value), color: Colors.blue, ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } }
2. Curved Animation
class CurvedAnimationExample extends StatefulWidget { @override _CurvedAnimationExampleState createState() => _CurvedAnimationExampleState(); } class _CurvedAnimationExampleState extends State<CurvedAnimationExample> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); _animation = CurvedAnimation( parent: _controller, curve: Curves.bounceOut, reverseCurve: Curves.easeIn, )..addListener(() { setState(() {}); }); _controller.repeat(reverse: true); } @override Widget build(BuildContext context) { return Center( child: Transform.translate( offset: Offset(0, -100 * _animation.value), child: Container( width: 100, height: 100, color: Colors.red, ), ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } }
Advanced Animation Techniques
1. Staggered Animation
class StaggeredAnimation extends StatefulWidget { @override _StaggeredAnimationState createState() => _StaggeredAnimationState(); } class _StaggeredAnimationState extends State<StaggeredAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _opacity; late Animation<double> _size; late Animation<Offset> _position; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); _opacity = Tween<double>(begin: 0, end: 1).animate( CurvedAnimation( parent: _controller, curve: const Interval(0, 0.3, curve: Curves.easeIn), ), ); _size = Tween<double>(begin: 0, end: 1).animate( CurvedAnimation( parent: _controller, curve: const Interval(0.3, 0.6, curve: Curves.easeOut), ), ); _position = Tween<Offset>( begin: const Offset(0, 1), end: Offset.zero, ).animate( CurvedAnimation( parent: _controller, curve: const Interval(0.6, 1, curve: Curves.easeInOut), ), ); _controller.forward(); } @override Widget build(BuildContext context) { return Center( child: SlideTransition( position: _position, child: FadeTransition( opacity: _opacity, child: ScaleTransition( scale: _size, child: Container( width: 100, height: 100, color: Colors.green, ), ), ), ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } }
2. Physics-Based Animation
class PhysicsAnimation extends StatefulWidget { @override _PhysicsAnimationState createState() => _PhysicsAnimationState(); } class _PhysicsAnimationState extends State<PhysicsAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; late SpringSimulation _spring; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 1), ); _spring = SpringSimulation( SpringDescription( mass: 1, stiffness: 100, damping: 10, ), 0, 1, 0, ); _animation = _controller.drive( Tween<double>(begin: 0, end: 1).chain( CurveTween(curve: Curves.easeInOut), ), ); _controller.forward(); } @override Widget build(BuildContext context) { return Center( child: AnimatedBuilder( animation: _animation, builder: (context, child) { return Transform.translate( offset: Offset(0, -200 * _spring.x(_animation.value)), child: Container( width: 100, height: 100, color: Colors.purple, ), ); }, ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } }
Custom Animation Widgets
1. Reusable Animation Widget
class AnimatedContainerWidget extends StatefulWidget { final Widget child; final Duration duration; final Curve curve; final double begin; final double end; const AnimatedContainerWidget({ Key? key, required this.child, this.duration = const Duration(milliseconds: 300), this.curve = Curves.easeInOut, this.begin = 0, this.end = 1, }) : super(key: key); @override _AnimatedContainerWidgetState createState() => _AnimatedContainerWidgetState(); } class _AnimatedContainerWidgetState extends State<AnimatedContainerWidget> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, vsync: this, ); _animation = CurvedAnimation( parent: _controller, curve: widget.curve, ); _controller.forward(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { return Transform.scale( scale: widget.begin + (widget.end - widget.begin) * _animation.value, child: widget.child, ); }, ); } @override void dispose() { _controller.dispose(); super.dispose(); } }
2. Custom Transition
class CustomTransition extends PageRouteBuilder { final Widget page; CustomTransition({required this.page}) : super( pageBuilder: (context, animation, secondaryAnimation) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( opacity: animation, child: ScaleTransition( scale: Tween<double>(begin: 0, end: 1).animate( CurvedAnimation( parent: animation, curve: Curves.easeOutBack, ), ), child: child, ), ); }, transitionDuration: const Duration(milliseconds: 500), ); }
Best Practices
-
Performance Optimization
- Use
AnimatedBuilder
for partial rebuilds - Dispose of controllers properly
- Use appropriate curves for smooth animations
- Use
-
Memory Management
- Clean up resources in dispose method
- Use const constructors where possible
- Avoid unnecessary rebuilds
-
Code Organization
- Create reusable animation widgets
- Separate animation logic from UI
- Document animation parameters
-
Testing
- Test animation completion
- Verify animation values
- Check performance impact
Common Issues and Solutions
-
Animation Jank
// Use appropriate curves _animation = CurvedAnimation( parent: _controller, curve: Curves.easeInOut, );
-
Memory Leaks
@override void dispose() { _controller.dispose(); super.dispose(); }
-
Performance Issues
// Use AnimatedBuilder for partial rebuilds return AnimatedBuilder( animation: _animation, builder: (context, child) { return Transform.scale( scale: _animation.value, child: child, ); }, child: expensiveWidget, );
Conclusion
Creating custom animations in Flutter allows you to build engaging and polished user interfaces. Remember to:
- Choose appropriate animation types
- Optimize for performance
- Handle memory management properly
- Test thoroughly
Happy animating!