Flutter Animations: A Complete Guide to Building Smooth UI Experiences
A good UI doesn’t only display information.
It communicates change.
When a screen appears, a card expands, a button changes state, or an item moves from one location to another, animation can help the user understand what happened.
Flutter has a powerful animation system that allows developers to create everything from simple fades to complex, coordinated animations.
In this article, we’ll build a strong foundation for understanding Flutter animations.
Why Do Animations Matter?
Without animation, a UI transition can feel abrupt.
For example:
Screen A
↓
Screen BWith animation:
Screen A
↓
Transition
↓
Screen BAnimation can communicate:
- Change
- Hierarchy
- Continuity
- Feedback
- Progress
- Focus
But animation should have a purpose.
Adding animation to every widget can make an application harder to use rather than better.
Flutter’s Animation System
Flutter provides several layers for creating animations.
The two major categories are:
Implicit animations
Explicit animationsImplicit animations are simpler.
Explicit animations provide much more control.
We’ll cover both in dedicated articles.
The Animation Lifecycle
An explicit Flutter animation commonly involves:
AnimationController
↓
Animation
↓
Tween
↓
Curve
↓
AnimatedBuilder / AnimatedWidget
↓
UIUnderstanding this relationship is important.
AnimationController
AnimationController controls the timing of an animation.
Example:
class ExampleWidget extends StatefulWidget {
const ExampleWidget({super.key});
@override
State<ExampleWidget> createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget>
with SingleTickerProviderStateMixin {
late final AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return const SizedBox();
}
}What Is vsync?
You may notice:
vsync: thisThe controller uses a Ticker to produce animation frames.
vsync helps synchronize the animation with the screen's refresh cycle and avoids unnecessary work when the animation isn't visible.
For a single controller:
with SingleTickerProviderStateMixinis commonly used.
Starting an Animation
Once you have a controller:
controller.forward();starts the animation from its current value toward its upper bound.
You can reverse it:
controller.reverse();You can repeat it:
controller.repeat();And stop it:
controller.stop();Animation Values
By default, an AnimationController produces values between:
0.0 → 1.0For example:
0.0
0.2
0.4
0.6
0.8
1.0These values don’t have to represent pixels.
They can represent any transition.
Tween
A Tween maps the controller's 0.0 → 1.0 range to another range.
For example:
final animation = Tween<double>(
begin: 0,
end: 100,
).animate(controller);Now:
controller = 0.0 → value = 0
controller = 0.5 → value = 50
controller = 1.0 → value = 100Color Tween
Tweens aren’t limited to numbers.
For example:
final animation = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(controller);You can animate between colors.
Offset Tween
You can animate positions:
final animation = Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset.zero,
).animate(controller);This is commonly used for slide animations.
Curves
Linear animation can feel mechanical.
Flutter provides curves such as:
Curves.easeIn
Curves.easeOut
Curves.easeInOut
Curves.fastOutSlowInYou can apply one with:
final animation = CurvedAnimation(
parent: controller,
curve: Curves.easeOut,
);Now the animation progresses according to the curve rather than changing at a constant rate.
Understanding Curves
Imagine moving from:
0 → 100A linear curve might behave approximately like:
0 25 50 75 100An ease-out curve may move quickly at first and slow down near the end.
The result feels more natural.
AnimatedBuilder
AnimatedBuilder rebuilds a portion of the widget tree whenever the animation changes.
Get Nisarg Ratani’s stories in your inbox
Join Medium for free to get updates from this writer.
Example:
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Transform.scale(
scale: controller.value,
child: child,
);
},
child: const Icon(Icons.favorite),
)Using the child parameter can avoid rebuilding widgets that don't depend on the animation.
Transform Widgets
Flutter provides several useful transformation widgets.
Scale
Transform.scale(
scale: 1.2,
child: child,
)Rotate
Transform.rotate(
angle: 0.5,
child: child,
)Translate
Transform.translate(
offset: const Offset(20, 0),
child: child,
)These are frequently used with animations.
Fade Animation
A simple explicit fade:
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Opacity(
opacity: controller.value,
child: child,
);
},
child: const Text('Hello'),
)The opacity changes from:
0 → 1Slide Animation
Using SlideTransition:
SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(controller),
child: const YourWidget(),
)This moves the widget into position.
Scale Animation
Using ScaleTransition:
ScaleTransition(
scale: controller,
child: const Icon(Icons.favorite),
)Rotation Animation
Using RotationTransition:
RotationTransition(
turns: controller,
child: const Icon(Icons.refresh),
)Animation Duration
Animation duration should communicate the amount of change.
For example:
const Duration(milliseconds: 200)may work well for a small interaction.
A larger transition may need more time.
There isn’t one universal duration.
The important principle is:
The animation should feel fast enough to avoid slowing the user down while still communicating the change.
Animation Curves
Choose curves based on the interaction.
Common choices:
Curves.easeIn
Curves.easeOut
Curves.easeInOut
Curves.linearFor example, an element entering the screen often benefits from an ease-out curve.
Chained Animations
You can create multiple stages using Interval.
final animation = CurvedAnimation(
parent: controller,
curve: const Interval(
0.0,
0.5,
curve: Curves.easeOut,
),
);Another animation can occupy:
const Interval(
0.5,
1.0,
curve: Curves.easeOut,
)This allows multiple UI elements to animate at different times.
Staggered Animations
Imagine:
Title → appears first
Description → appears second
Button → appears thirdYou can coordinate them using intervals.
Conceptually:
0% 30% 60% 100%
|---------|---------|----------|
Title
Description
ButtonThis creates a staggered animation.
Don’t Animate Everything
A common beginner mistake is adding animation everywhere.
For example:
Every card
Every icon
Every text
Every button
Every list item
Every screenThis can make the UI noisy.
Instead, animate meaningful state changes.
Animation Performance
Animations run frequently, so performance matters.
Avoid unnecessarily rebuilding large widget trees.
Prefer:
AnimatedBuilder(
animation: animation,
child: ExpensiveWidget(),
builder: (context, child) {
return Transform.scale(
scale: animation.value,
child: child,
);
},
)The static child doesn’t need to rebuild on every animation tick.
Dispose Controllers
Every manually created AnimationController should be disposed.
@override
void dispose() {
controller.dispose();
super.dispose();
}Failing to do so can cause resource leaks.
Accessibility and Motion
Not every user experiences animations in the same way.
Flutter exposes accessibility information through MediaQuery.
For example, applications can consider:
MediaQuery.disableAnimations(context)when deciding whether certain non-essential animations should be reduced or disabled.
Essential state communication should never depend solely on animation.
When to Use Which Animation Approach
Use implicit animations when:
Simple property changes
Small interactions
No need for manual controlUse explicit animations when:
Precise timing
Reverse/repeat control
Multiple animation stages
Complex transitions
Animation synchronizationUse specialized widgets such as:
FadeTransition
ScaleTransition
SlideTransition
RotationTransitionwhen they directly match the desired effect.
Production Animation Checklist
Before adding an animation, ask:
- Does it communicate a state change?
- Does it improve the user’s understanding?
- Is the duration appropriate?
- Is the curve appropriate?
- Does it remain smooth?
- Does it rebuild only what is necessary?
- Does it behave correctly when interrupted?
- Does it work with accessibility settings?
Conclusion
Flutter’s animation system is powerful because it separates:
Timing
↓
Animation value
↓
Transformation
↓
WidgetThe core concepts to understand are:
AnimationController
Animation
Tween
CurvedAnimation
Curve
AnimatedBuilder
TransitionsOnce these concepts are clear, you can build everything from a simple fade to a complex onboarding animation.
In the next article, we’ll simplify things considerably and explore implicit animations, where Flutter handles much of the animation machinery for you.





