<h1 id="building-custom-animations-in-flutter">Building Custom Animations in Flutter</h1> <p>Flutter provides powerful tools for creating custom animations. This article will guide you through building custom animations using AnimationController, Tween, and custom painters.</p> <h2 id="understanding-animation-basics">Understanding Animation Basics</h2> <h3 id="animationcontroller">1. AnimationController</h3> <pre>class CustomAnimation extends StatefulWidget { @override _CustomAnimationState createState() => _CustomAnimationState(); }
class _CustomAnimationState extends State<CustomAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, )..repeat(); }
@override void dispose() { _controller.dispose(); super.dispose(); } } </pre> <h3 id="tween-and-curvedanimation">2. Tween and CurvedAnimation</h3> <pre>class _CustomAnimationState extends State<CustomAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, );
_animation = Tween&lt;double&gt;(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
_controller.repeat();
} } </pre> <h2 id="creating-custom-animations">Creating Custom Animations</h2> <h3 id="custom-painter-animation">1. Custom Painter Animation</h3> <pre>class CustomPainterAnimation extends CustomPainter { final Animation<double> animation;
CustomPainterAnimation(this.animation) : super(repaint: animation);
@override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..style = PaintingStyle.fill;
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 4 * animation.value;
canvas.drawCircle(center, radius, paint);
}
@override bool shouldRepaint(CustomPainterAnimation oldDelegate) { return oldDelegate.animation != animation; } } </pre> <h3 id="using-custom-painter">2. Using Custom Painter</h3> <pre>class AnimatedCircle extends StatelessWidget { @override Widget build(BuildContext context) { return CustomPaint( painter: CustomPainterAnimation( Tween<double>(begin: 0, end: 1).animate( AnimationController( duration: Duration(seconds: 2), vsync: this, )..repeat(), ), ), child: Container(), ); } } </pre> <h2 id="advanced-animation-techniques">Advanced Animation Techniques</h2> <h3 id="staggered-animations">1. Staggered Animations</h3> <pre>class StaggeredAnimation extends StatefulWidget { @override _StaggeredAnimationState createState() => _StaggeredAnimationState(); }
class _StaggeredAnimationState extends State<StaggeredAnimation> with TickerProviderStateMixin { late AnimationController _controller; late List<Animation<double>> _animations;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, );
_animations = List.generate(3, (index) {
return Tween&lt;double&gt;(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Interval(
index * 0.3,
(index + 1) * 0.3,
curve: Curves.easeInOut,
),
));
});
_controller.forward();
} } </pre> <h3 id="physics-based-animations">2. Physics-Based Animations</h3> <pre>class PhysicsAnimation extends StatefulWidget { @override _PhysicsAnimationState createState() => _PhysicsAnimationState(); }
class _PhysicsAnimationState extends State<PhysicsAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, );
_animation = Tween&lt;double&gt;(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.bounceOut,
));
_controller.forward();
} } </pre> <h2 id="performance-optimization">Performance Optimization</h2> <h3 id="using-repaintboundary">1. Using RepaintBoundary</h3> <pre>RepaintBoundary( child: CustomPaint( painter: CustomPainterAnimation(_animation), child: Container(), ), ) </pre> <h3 id="optimizing-animation-updates">2. Optimizing Animation Updates</h3> <pre>class OptimizedAnimation extends StatefulWidget { @override _OptimizedAnimationState createState() => _OptimizedAnimationState(); }
class _OptimizedAnimationState extends State<OptimizedAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, );
_animation = Tween&lt;double&gt;(
begin: 0.0,
end: 1.0,
).animate(_controller);
_controller.addListener(() {
if (_controller.isCompleted) {
_controller.reverse();
} else if (_controller.isDismissed) {
_controller.forward();
}
});
_controller.forward();
} } </pre> <h2 id="best-practices">Best Practices</h2> <ol> <li><strong>Use Appropriate Curves</strong>: Choose curves that match the animation's purpose</li> <li><strong>Optimize Performance</strong>: Use RepaintBoundary for complex animations</li> <li><strong>Handle Disposal</strong>: Always dispose of AnimationControllers</li> <li><strong>Test on Multiple Devices</strong>: Ensure smooth performance across devices</li> <li><strong>Consider Accessibility</strong>: Provide alternative content for users who prefer reduced motion</li> </ol> <h2 id="example-custom-loading-animation">Example: Custom Loading Animation</h2> <pre>class CustomLoadingAnimation extends StatefulWidget { @override _CustomLoadingAnimationState createState() => _CustomLoadingAnimationState(); }
class _CustomLoadingAnimationState extends State<CustomLoadingAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _rotationAnimation; late Animation<double> _scaleAnimation;
@override void initState() { super.initState(); _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, );
_rotationAnimation = Tween&lt;double&gt;(
begin: 0.0,
end: 2 * pi,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.linear,
));
_scaleAnimation = Tween&lt;double&gt;(
begin: 0.5,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
_controller.repeat();
}
@override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.rotate( angle: _rotationAnimation.value, child: Transform.scale( scale: _scaleAnimation.value, child: Icon(Icons.refresh, size: 48), ), ); }, ); }
@override void dispose() { _controller.dispose(); super.dispose(); } } </pre> <h2 id="conclusion">Conclusion</h2> <p>Building custom animations in Flutter involves:</p> <ul> <li>Understanding AnimationController and Tween</li> <li>Creating custom painters for complex animations</li> <li>Implementing staggered and physics-based animations</li> <li>Optimizing performance with RepaintBoundary</li> <li>Following best practices for smooth animations</li> </ul> <ol> <li><p><strong>Performance</strong></p> <ul> <li>Use <code>const</code> widgets where possible</li> <li>Avoid unnecessary rebuilds</li> <li>Use <code>RepaintBoundary</code> for complex animations</li> <li>Optimize animation curves</li> </ul> </li> <li><p><strong>User Experience</strong></p> <ul> <li>Keep animations short and purposeful</li> <li>Provide clear feedback</li> <li>Consider motion sickness</li> <li>Respect user preferences</li> </ul> </li> <li><p><strong>Code Organization</strong></p> <ul> <li>Separate animation logic</li> <li>Use mixins for common behaviors</li> <li>Implement proper cleanup</li> <li>Document animation parameters</li> </ul> </li> </ol> <h2 id="common-animation-patterns">Common Animation Patterns</h2> <h3 id="page-transitions">1. Page Transitions</h3> <pre>class CustomPageRoute extends PageRouteBuilder { final Widget page;
CustomPageRoute() : super( pageBuilder: (context, animation, secondaryAnimation) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( opacity: animation, child: child, ); }, ); }
// Usage Navigator.push( context, CustomPageRoute(page: const NextPage()), ); </pre> <h3 id="loading-animations">2. Loading Animations</h3> <pre>class LoadingAnimation extends StatefulWidget { const LoadingAnimation();
@override State<LoadingAnimation> createState() => _LoadingAnimationState(); }
class _LoadingAnimationState extends State<LoadingAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _rotationAnimation;
@override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 2), );
_rotationAnimation = Tween&lt;double&gt;(
begin: 0,
end: 2 * math.pi,
).animate(_controller);
_controller.repeat();
}
@override Widget build(BuildContext context) { return RotationTransition( turns: _rotationAnimation, child: const Icon(Icons.refresh, size: 50), ); } } </pre> <h3 id="interactive-animations">3. Interactive Animations</h3> <pre>class InteractiveAnimation extends StatefulWidget { const InteractiveAnimation();
@override State<InteractiveAnimation> createState() => _InteractiveAnimationState(); }
class _InteractiveAnimationState extends State<InteractiveAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; double _dragPosition = 0;
@override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), );
_animation = Tween&lt;double&gt;(begin: 0, end: 1).animate(_controller);
}
@override Widget build(BuildContext context) { return GestureDetector( onPanUpdate: (details) { setState(() { dragPosition += details.delta.dx; }); }, onPanEnd: () { if (_dragPosition > 100) { _controller.forward(); } else { _controller.reverse(); } }, child: Transform.translate( offset: Offset(_dragPosition, 0), child: const FlutterLogo(size: 100), ), ); } } </pre> <h2 id="testing-animations">Testing Animations</h2> <pre>void main() { group('Animation Tests', () { testWidgets('animates correctly', (tester) async { await tester.pumpWidget(const MaterialApp( home: BasicAnimation(), ));
expect(find.byType(FlutterLogo), findsOneWidget);
await tester.pump(const Duration(seconds: 1));
// Verify animation state
});
testWidgets(&#39;handles user interaction&#39;, (tester) async {
await tester.pumpWidget(const MaterialApp(
home: InteractiveAnimation(),
));
await tester.drag(find.byType(FlutterLogo), const Offset(200, 0));
await tester.pumpAndSettle();
// Verify final position
});
}); } </pre> <h2 id="performance-optimization-1">Performance Optimization</h2> <ol> <li><p><strong>Use RepaintBoundary</strong></p> <pre>RepaintBoundary( child: AnimatedContainer( duration: const Duration(seconds: 1), color: Colors.blue, ), ) </pre> </li> <li><p><strong>Optimize Animation Curves</strong></p> <pre>CurvedAnimation( parent: _controller, curve: Curves.easeOutCubic, // More performant than easeOut ) </pre> </li> <li><p><strong>Use Transform Instead of Layout Changes</strong></p> <pre>Transform.scale( scale: _animation.value, child: const FlutterLogo(), ) </pre> </li> </ol> <h2 id="conclusion-1">Conclusion</h2> <p>Creating custom animations in Flutter requires understanding of:</p> <ol> <li><strong>Animation Fundamentals</strong>: Controllers, tweens, and curves</li> <li><strong>Performance Considerations</strong>: Optimization and best practices</li> <li><strong>User Experience</strong>: Purposeful and smooth animations</li> <li><strong>Advanced Techniques</strong>: Physics, staggered, and custom animations</li> <li><strong>Testing</strong>: Verifying animation behavior</li> </ol> <p>Remember to:</p> <ul> <li>Keep animations purposeful and smooth</li> <li>Optimize for performance</li> <li>Test thoroughly</li> <li>Consider accessibility</li> <li>Document animation parameters</li> </ul> <p>By following these guidelines, you can create engaging and performant animations that enhance your Flutter applications.</p>