Animations
Creating smooth, engaging animations in Flutter — from simple fades to complex physics-based motion.
Well-designed animations make a UI feel more intuitive , contribute to the slick look and feel of a polished app, and improve the user experience . Flutter's animation support makes it easy to implement a variety of animation types.
💡 Key Insight
Animations aren't just about making things look pretty — they provide feedback , guide attention , and communicate state changes to users. A well-animated app feels more responsive and professional.
Flutter provides two main approaches to animations: implicit and explicit . Understanding the difference is key to choosing the right approach.
Implicit Animations
- Framework handles the animation
- Set target values, Flutter animates to them
- Easiest to implement
- Less control, but less code
-
Example:
AnimatedContainer,AnimatedOpacity
Explicit Animations
- You control the animation
-
Use
AnimationController - More complex, but full control
- Can pause, reverse, and loop
-
Example:
AnimatedBuilder, custom animations
🤔 How to Choose?
class AnimatedBox extends StatefulWidget {
@override
_AnimatedBoxState createState() => _AnimatedBoxState();
}
class _AnimatedBoxState extends State<AnimatedBox> {
double _size = 100;
Color _color = Colors.blue;
void _toggle() {
setState(() {
_size = _size == 100 ? 200 : 100;
_color = _color == Colors.blue ? Colors.red : Colors.blue;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedContainer(
width: _size,
height: _size,
color: _color,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
),
ElevatedButton(onPressed: _toggle, child: Text('Toggle')),
],
);
}
}
class ExplicitAnimation extends StatefulWidget {
@override
_ExplicitAnimationState createState() => _ExplicitAnimationState();
}
class _ExplicitAnimationState extends State<ExplicitAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _controller.value,
child: Container(width: 100, height: 100, color: Colors.purple),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _controller.forward(),
child: Text('Play'),
),
ElevatedButton(
onPressed: () => _controller.reverse(),
child: Text('Reverse'),
),
ElevatedButton(
onPressed: () => _controller.reset(),
child: Text('Reset'),
),
],
),
],
);
}
}
Flutter supports two main types of animations: tween-based and physics-based .
final controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
final animation = Tween<double>(
begin: 0,
end: 300,
).animate(CurvedAnimation(parent: controller, curve: Curves.easeInOut));
import
'package:flutter/physics.dart'
;
final
controller = AnimationController(
duration: Duration(seconds:
2
),
vsync:
this
,
);
final
spring = SpringSimulation(
spring: SpringDescription(
mass:
1
,
stiffness:
100
,
damping:
10
,
),
start:
0
,
end:
300
,
velocity:
0
,
);
controller.
animateWith
(spring);
Flutter's animation system is built on several key classes that work together:
AnimationController
A special
Animation
object that generates new values on each frame.
It produces values from 0.0 to 1.0 over a given duration.
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
// Required for performance
duration: Duration(seconds: 1),
);
}
@override
void dispose() {
_controller.dispose();
// Always dispose!
super.dispose();
}
}
Tween
Defines the interpolation between two values. While
AnimationController
produces values from 0.0 to 1.0,
Tween
maps these to your desired range or type.
// Double Tween
final tween = Tween<double>(begin: 0, end: 200);
// Color Tween
final colorTween = ColorTween(begin: Colors.blue, end: Colors.red);
// Size Tween
final sizeTween = Tween<Size>(begin: Size(50, 50), end: Size(200, 200));
// Int Tween
final intTween = IntTween(begin: 0, end: 100);
CurvedAnimation
Applies a non-linear curve to an animation's progress. Flutter provides many
pre-defined curves in the
Curves
class.
final curvedAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
reverseCurve: Curves.easeOutIn,
// Optional
);
final animation = Tween<double>(begin: 0, end: 100).animate(curvedAnimation);
1. Fade In/Out
class FadeAnimation extends StatefulWidget {
@override
_FadeAnimationState createState() => _FadeAnimationState();
}
class _FadeAnimationState extends State<FadeAnimation>
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);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
FadeTransition(opacity: _animation, child: FlutterLogo(size: 100)),
ElevatedButton(
onPressed: () {
if (_controller.status == AnimationStatus.completed) {
_controller.reverse();
} else {
_controller.forward();
}
},
child: Text('Toggle Fade'),
),
],
);
}
}
2. Scale Animation
class ScaleAnimation extends StatelessWidget {
final AnimationController controller;
ScaleAnimation({required this.controller});
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: Tween<double>(
begin: 0.5,
end: 1.5,
).animate(CurvedAnimation(parent: controller, curve: Curves.easeOutBack)),
child: Container(width: 100, height: 100, color: Colors.green),
);
}
}
3. Rotation Animation
class RotationAnimation extends StatelessWidget {
final AnimationController controller;
RotationAnimation({required this.controller});
@override
Widget build(BuildContext context) {
return RotationTransition(
turns: Tween<double>(begin: 0, end: 1).animate(controller),
child: Container(width: 100, height: 100, color: Colors.orange),
);
}
}
4. Animated List
class AnimatedListWidget extends StatefulWidget {
@override
_AnimatedListWidgetState createState() => _AnimatedListWidgetState();
}
class _AnimatedListWidgetState extends State<AnimatedListWidget> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final List<String> _items = [];
int _counter = 0;
void _addItem() {
_counter++;
_items.insert(0, 'Item $_counter');
_listKey.currentState?.insertItem(0);
}
void _removeItem(int index) {
_items.removeAt(index);
_listKey.currentState?.removeItem(index, (context, animation) {
return SizeTransition(
sizeFactor: animation,
child: Card(child: ListTile(title: Text('Removing...'))),
);
}, duration: Duration(milliseconds: 300));
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: _addItem, child: Text('Add Item')),
Expanded(
child: AnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return SizeTransition(
sizeFactor: animation,
child: ListTile(
title: Text(_items[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _removeItem(index),
),
),
);
},
),
),
],
);
}
}
Follow these steps to implement animations in your Flutter app:
SingleTickerProviderStateMixin
to your state
CurvedAnimation
for non-linear motion
forward()
,
reverse()
, or
repeat()
dispose()
on controllers
Not disposing
AnimationController
causes memory leaks and performance issues.
Always call
_controller.dispose()
in
dispose()
method of your State.
Calling
setState()
in animation listeners rebuilds too much and hurts performance.
Use
AnimatedBuilder
or
AnimatedWidget
to rebuild only the animated parts.
Hardcoding animation durations makes it harder to adjust for different devices and preferences.
Define animation durations in a central place or use Theme data for consistent timing.
Adding animations everywhere can make the app feel slow and overwhelming.
Use animations to guide attention, provide feedback, and communicate state changes — not just for decoration.
🎯 Key Takeaway
Animations
are essential for creating polished, intuitive Flutter apps.
Use
implicit animations
for simple cases and
explicit animations
with
AnimationController
for full control. Remember to
dispose
your controllers and use
curves
for natural-looking motion. Always animate
with purpose — to
guide attention
,
provide feedback
, or
communicate state changes
.