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
.