Explicit Animations
Full control over Flutter animations
🎯 What are Explicit Animations?
Explicit animations give you complete control over animation timing and behavior using AnimationController. They're perfect for complex animations, repeating effects, and custom motion patterns requiring precise control over start, stop, reverse, and repeat actions.
// Full control with AnimationController
AnimationController controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
controller.forward();
Key Components
AnimationController
Controls animation playback
controller.forward()
controller.reverse()
controller.repeat()
Tween
Defines animation range
Tween(
begin: 0.0,
end: 300.0,
)
AnimatedBuilder
Rebuilds on animation changes
AnimatedBuilder(
animation: controller,
builder: (context, child) {},
)
TickerProvider
Provides animation timing
with SingleTickerProviderStateMixin
🔹 Basic AnimationController Setup
Setting up an explicit animation:
class ExplicitAnimationDemo extends StatefulWidget {
@override
_ExplicitAnimationDemoState createState() => _ExplicitAnimationDemoState();
}
class _ExplicitAnimationDemoState extends State
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
super.initState();
// Create controller
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// Create animation with Tween
_animation = Tween(
begin: 0.0,
end: 300.0,
).animate(_controller);
}
@override
void dispose() {
_controller.dispose(); // Always dispose!
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
color: Colors.blue,
);
},
);
}
}
🔹 Controlling Animations
Different ways to control animation playback:
class AnimationControls extends StatefulWidget {
@override
_AnimationControlsState createState() => _AnimationControlsState();
}
class _AnimationControlsState extends State
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () => _controller.forward(),
child: Text('Play Forward'),
),
ElevatedButton(
onPressed: () => _controller.reverse(),
child: Text('Play Reverse'),
),
ElevatedButton(
onPressed: () => _controller.repeat(),
child: Text('Repeat'),
),
ElevatedButton(
onPressed: () => _controller.stop(),
child: Text('Stop'),
),
ElevatedButton(
onPressed: () => _controller.reset(),
child: Text('Reset'),
),
],
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
🔹 Rotating Animation Example
Create a continuously rotating widget:
class RotatingSquare extends StatefulWidget {
@override
_RotatingSquareState createState() => _RotatingSquareState();
}
class _RotatingSquareState extends State
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 3),
vsync: this,
)..repeat(); // Automatically repeat
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _controller.value * 2 * 3.14159, // Full rotation
child: Container(
width: 100,
height: 100,
color: Colors.red,
child: Center(
child: Text(
'Spin',
style: TextStyle(color: Colors.white),
),
),
),
);
},
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
🔹 Curved Animation Example
Add curves to explicit animations:
class CurvedAnimationDemo extends StatefulWidget {
@override
_CurvedAnimationDemoState createState() => _CurvedAnimationDemoState();
}
class _CurvedAnimationDemoState extends State
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// Add curve to animation
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.bounceOut,
);
// Apply Tween
_animation = Tween(
begin: 50.0,
end: 300.0,
).animate(_animation);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: _animation.value,
height: 100,
color: Colors.green,
);
},
),
ElevatedButton(
onPressed: () {
_controller.reset();
_controller.forward();
},
child: Text('Animate'),
),
],
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
🔹 Multiple Tweens Example
Animate multiple properties simultaneously:
class MultiTweenAnimation extends StatefulWidget {
@override
_MultiTweenAnimationState createState() => _MultiTweenAnimationState();
}
class _MultiTweenAnimationState extends State
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _sizeAnimation;
late Animation _colorAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// Size animation
_sizeAnimation = Tween(
begin: 50.0,
end: 200.0,
).animate(_controller);
// Color animation
_colorAnimation = ColorTween(
begin: Colors.blue,
end: Colors.purple,
).animate(_controller);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
decoration: BoxDecoration(
color: _colorAnimation.value,
borderRadius: BorderRadius.circular(20),
),
);
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_controller.status == AnimationStatus.completed) {
_controller.reverse();
} else {
_controller.forward();
}
},
child: Text('Toggle Animation'),
),
],
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
🔹 Animation Status Listeners
React to animation state changes:
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// Listen to status changes
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
print('Animation completed!');
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
print('Animation dismissed!');
_controller.forward();
}
});
// Listen to value changes
_controller.addListener(() {
print('Current value: ${_controller.value}');
});
_controller.forward();
}
💡 Explicit Animation Best Practices:
- Always dispose: Call controller.dispose() to prevent memory leaks
- Use vsync: Required for smooth animations and battery efficiency
- SingleTickerProvider: Use for one controller, MultiTickerProvider for multiple
- AnimatedBuilder: Optimizes rebuilds to only animated widgets
- Status listeners: Use to chain animations or trigger actions