Creating Smooth Animations with AnimationController
Creating Smooth Animations with AnimationController
What You’ll Learn
Master Flutter’s AnimationController to create custom, buttery-smooth animations. This is the foundation for all animations in Flutter—from simple fades to complex transitions.
Why AnimationController?
Flutter’s implicit animations (like AnimatedContainer) are great for simple cases, but AnimationController gives you full control over timing, curves, and sequencing. It’s essential for building polished, professional UIs.
Example: Animated Logo with Fade and Scale
import 'package:flutter/material.dart';
class AnimatedLogo extends StatefulWidget {
@override
State<AnimatedLogo> createState() => _AnimatedLogoState();
}
class _AnimatedLogoState extends State<AnimatedLogo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
// Create the controller
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this, // Prevents offscreen animations
);
// Fade from 0 to 1
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
),
);
// Scale from 0.5 to 1.0
_scaleAnimation = Tween<double>(
begin: 0.5,
end: 1.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut,
),
);
// Start the animation
_controller.forward();
}
@override
void dispose() {
_controller.dispose(); // Always clean up!
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Animation Demo')),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: Transform.scale(
scale: _scaleAnimation.value,
child: child,
),
);
},
child: FlutterLogo(size: 200),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Toggle animation direction
if (_controller.isCompleted) {
_controller.reverse();
} else {
_controller.forward();
}
},
child: Icon(Icons.play_arrow),
),
);
}
}
Key Concepts Explained
1. SingleTickerProviderStateMixin
- Required for
vsyncparameter - Prevents animations when widget is offscreen (saves battery)
2. Tween
- Defines start and end values for your animation
- Works with any type:
double,Color,Size, etc.
3. CurvedAnimation
- Adds easing to make animations feel natural
- Common curves:
easeIn,easeOut,bounceIn,elasticOut
4. AnimatedBuilder
- Rebuilds only the animated widget (efficient!)
- The
childparameter avoids rebuilding static children
Animation Control Methods
_controller.forward(); // Start animation
_controller.reverse(); // Play backward
_controller.repeat(); // Loop forever
_controller.reset(); // Back to start
_controller.stop(); // Pause where it is
_controller.animateTo(0.5); // Go to specific value
Advanced: Sequential Animations
Animate multiple properties in sequence:
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 3),
vsync: this,
);
// First half: fade in (0.0 to 0.5)
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 0.5, curve: Curves.easeIn),
),
);
// Second half: scale (0.5 to 1.0)
_scaleAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.5, 1.0, curve: Curves.bounceOut),
),
);
_controller.forward();
}
The Interval curve splits your animation timeline into segments!
Try It Yourself
Build a loading spinner with these features:
- Rotate continuously using
_controller.repeat() - Add a
RotationTransitionwidget - Use
Tween<double>(begin: 0, end: 1)for a full rotation - Combine with a pulsing scale animation
Challenge: Create a “like” button that scales up and changes color when tapped, then bounces back. Use ColorTween and combine it with your scale animation.
Tip of the Day
Performance: Use AnimatedBuilder instead of setState() for animations. AnimatedBuilder only rebuilds the animated widget, while setState() rebuilds the entire widget tree.
Debugging: Add a listener to see animation values in real-time:
_controller.addListener(() {
print('Animation value: ${_controller.value}');
});
Use Flutter DevTools’ animation inspector to visualize and slow down animations for debugging.