Introduction to Flutter Animations
Animations are a crucial part of modern mobile applications, making them more engaging and providing visual feedback to users. Flutter provides a rich set of animation tools that make it easy to create beautiful and performant animations. This guide will introduce you to the fundamentals of animations in Flutter.
Types of Animations
1. Implicit Animations
Implicit animations are the simplest way to add animations to your Flutter app. They automatically animate changes to widget properties.
class AnimatedContainerExample extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedContainer( duration: Duration(seconds: 1), width: 200, height: 200, color: Colors.blue, curve: Curves.easeInOut, child: Center( child: Text('Tap to animate'), ), ); } }
Common implicit animation widgets:
AnimatedContainer
AnimatedOpacity
AnimatedPadding
AnimatedPositioned
AnimatedAlign
2. Explicit Animations
Explicit animations give you more control over the animation process using AnimationController
.
class ExplicitAnimationExample extends StatefulWidget { @override _ExplicitAnimationExampleState createState() => _ExplicitAnimationExampleState(); } class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: 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 Transform.scale( scale: _animation.value, child: Container( width: 100, height: 100, color: Colors.red, ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } }
Animation Controllers
Animation controllers manage the animation's state and timing.
class AnimationControllerExample extends StatefulWidget { @override _AnimationControllerExampleState createState() => _AnimationControllerExampleState(); } class _AnimationControllerExampleState extends State<AnimationControllerExample> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, ); _animation = CurvedAnimation( parent: _controller, curve: Curves.easeInOut, ); } void _startAnimation() { _controller.forward(); } void _reverseAnimation() { _controller.reverse(); } void _stopAnimation() { _controller.stop(); } @override Widget build(BuildContext context) { return Column( children: [ FadeTransition( opacity: _animation, child: Container( width: 100, height: 100, color: Colors.blue, ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: _startAnimation, child: Text('Start'), ), ElevatedButton( onPressed: _reverseAnimation, child: Text('Reverse'), ), ElevatedButton( onPressed: _stopAnimation, child: Text('Stop'), ), ], ), ], ); } }
Animation Curves
Curves define how an animation progresses over time.
class AnimationCurvesExample extends StatelessWidget { final List<Curve> curves = [ Curves.linear, Curves.easeIn, Curves.easeOut, Curves.easeInOut, Curves.bounceIn, Curves.bounceOut, Curves.elasticIn, Curves.elasticOut, ]; @override Widget build(BuildContext context) { return ListView.builder( itemCount: curves.length, itemBuilder: (context, index) { return TweenAnimationBuilder<double>( duration: Duration(seconds: 2), curve: curves[index], tween: Tween(begin: 0.0, end: 1.0), builder: (context, value, child) { return Container( margin: EdgeInsets.all(8), height: 50, width: value * 300, color: Colors.blue, child: Center( child: Text(curves[index].toString()), ), ); }, ); }, ); } }
Custom Animations
1. Using Tween
class CustomTweenAnimation extends StatefulWidget { @override _CustomTweenAnimationState createState() => _CustomTweenAnimationState(); } class _CustomTweenAnimationState extends State<CustomTweenAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<Color?> _colorAnimation; late Animation<double> _sizeAnimation; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, ); _colorAnimation = ColorTween( begin: Colors.red, end: Colors.blue, ).animate(_controller); _sizeAnimation = Tween<double>( begin: 50, end: 200, ).animate(_controller); _controller.repeat(reverse: true); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Container( width: _sizeAnimation.value, height: _sizeAnimation.value, color: _colorAnimation.value, ); }, ); } }
2. Using CustomPainter
class CustomPainterAnimation extends StatefulWidget { @override _CustomPainterAnimationState createState() => _CustomPainterAnimationState(); } class _CustomPainterAnimationState extends State<CustomPainterAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, ); _animation = Tween<double>( begin: 0, end: 2 * math.pi, ).animate(_controller); _controller.repeat(); } @override Widget build(BuildContext context) { return CustomPaint( painter: CirclePainter(_animation.value), size: Size(200, 200), ); } } class CirclePainter extends CustomPainter { final double angle; CirclePainter(this.angle); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..style = PaintingStyle.stroke ..strokeWidth = 4; final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 3; canvas.drawCircle(center, radius, paint); final x = center.dx + radius * math.cos(angle); final y = center.dy + radius * math.sin(angle); canvas.drawCircle(Offset(x, y), 10, paint); } @override bool shouldRepaint(CirclePainter oldDelegate) => oldDelegate.angle != angle; }
Best Practices
1. Performance Optimization
class OptimizedAnimation extends StatelessWidget { @override Widget build(BuildContext context) { return RepaintBoundary( child: AnimatedContainer( duration: Duration(milliseconds: 300), // Use const constructor for child widgets child: const Text('Optimized Animation'), ), ); } }
2. Memory Management
class MemoryManagedAnimation extends StatefulWidget { @override _MemoryManagedAnimationState createState() => _MemoryManagedAnimationState(); } class _MemoryManagedAnimationState extends State<MemoryManagedAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, ); } @override void dispose() { _controller.dispose(); // Always dispose controllers super.dispose(); } }
Common Animation Patterns
1. Page Transitions
class CustomPageRoute extends PageRouteBuilder { final Widget page; CustomPageRoute({required this.page}) : super( pageBuilder: (context, animation, secondaryAnimation) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( opacity: animation, child: child, ); }, ); }
2. Hero Animations
class HeroAnimationExample extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => DetailPage(), ), ); }, child: Hero( tag: 'imageHero', child: Image.network('https://example.com/image.jpg'), ), ); } }
Conclusion
Flutter provides a powerful and flexible animation system that allows you to:
- Create simple implicit animations
- Build complex explicit animations
- Customize animation curves
- Optimize performance
- Manage memory efficiently
Remember to:
- Choose the right type of animation for your use case
- Use animation controllers responsibly
- Dispose of controllers when they're no longer needed
- Optimize animations for performance
- Test animations on different devices
With these fundamentals, you can create engaging and performant animations in your Flutter applications!