Good animation is what separates an app that works from an app that feels alive. Flutter makes this unusually approachable: a whole family of Animated* widgets animate for you with a single property change, and when you need real control, the AnimationController and Tween system gives you frame-accurate command of the timeline. This guide covers both, from the one-line implicit animation to staggered choreography and custom page transitions, with runnable code throughout.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowEvery snippet is paste-ready against current stable Flutter. The golden rule before you start: reach for an implicit animation first. Most UI motion is a simple state-driven change, and the Animated* widgets handle those with no controller and no lifecycle to manage.
We will work up the ladder: implicit animations, then AnimatedSwitcher and Hero, then the explicit AnimationController and Tween system, then choreography (staggered animations), custom page-route transitions, and finally CustomPaint for drawing your own animated graphics. A short section on performance closes it out.
This is one of three Flutter pillar guides published together. The Flutter Scrolling and Slivers guide and the Dart Language for Flutter guide cover the scrolling surfaces you will animate and the language every callback is written in.
Implicit animations: the Animated* family
An implicit animation watches one or more properties and animates whenever they change. You give it a duration and a curve; Flutter does the in-between frames. The headline widget is AnimatedContainer — change its colour, size, padding, or alignment in setState and it smoothly transitions.
class _Box extends StatefulWidget {
const _Box();
@override
State<_Box> createState() => _BoxState();
}
class _BoxState extends State<_Box> {
bool _big = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _big = !_big),
child: AnimatedContainer(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
width: _big ? 200 : 100,
height: _big ? 200 : 100,
decoration: BoxDecoration(
color: _big ? Colors.deepPurple : Colors.amber,
borderRadius: BorderRadius.circular(_big ? 32 : 8),
),
),
);
}
}
The family is large and you rarely need more than these:
AnimatedOpacity— fade a widget in and out by changingopacity.AnimatedAlign/AnimatedPositioned— slide a child to a new position (the latter inside aStack).AnimatedPadding/AnimatedDefaultTextStyle— animate spacing and text style.AnimatedCrossFade— cross-fade between exactly two children.TweenAnimationBuilder— animate any value once, on first build, without writing a controller.
TweenAnimationBuilder deserves a special mention because it covers the gap between implicit and explicit: you give it a Tween and a builder, and it animates from begin to end whenever the end value changes.
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: 1),
duration: const Duration(milliseconds: 600),
curve: Curves.easeOutBack,
builder: (context, value, child) => Transform.scale(
scale: value,
child: child,
),
child: const FlutterLogo(size: 96),
)
AnimatedSwitcher: animate content swaps
When the content changes rather than a property — a number ticking up, an icon toggling — AnimatedSwitcher cross-fades the old child out and the new one in. Give each child a unique Key so the switcher knows they differ.
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) =>
ScaleTransition(scale: animation, child: child),
child: Text(
'$count',
key: ValueKey<int>(count),
style: const TextStyle(fontSize: 48),
),
)
Hero: shared-element transitions between screens
A Hero animation flies a widget from one screen to the next. Wrap the widget on both routes in a Hero with the same tag, and Flutter interpolates its position and size during navigation — no controller required.
// On the list screen
Hero(
tag: 'avatar-$id',
child: CircleAvatar(backgroundImage: NetworkImage(url)),
)
// On the detail screen — same tag
Hero(
tag: 'avatar-$id',
child: CircleAvatar(radius: 80, backgroundImage: NetworkImage(url)),
)
The tags must be unique within a screen and identical across the two routes. A duplicate tag on one screen throws, so derive the tag from a stable id.

Make your UI feel premium
The Complete Flutter Guide shows you how to weave animation into real screens without hurting performance.
Enrol nowExplicit animations: AnimationController and Tween
When you need to loop, reverse, or coordinate several animations from one timeline, you step up to an AnimationController. It produces a value from 0.0 to 1.0 over its duration, driven by a Ticker that fires once per frame. Because it needs a ticker, the State must mix in SingleTickerProviderStateMixin and pass vsync: this. Always dispose the controller.
class _Pulse extends StatefulWidget {
const _Pulse();
@override
State<_Pulse> createState() => _PulseState();
}
class _PulseState extends State<_Pulse>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _scale;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
)..repeat(reverse: true);
_scale = Tween<double>(begin: 0.9, end: 1.2).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: _scale,
child: const Icon(Icons.favorite, color: Colors.red, size: 64),
);
}
}
Three ideas do all the work here. The controller owns the timeline (forward, reverse, repeat, stop). The Tween maps the 0–1 value onto the range you care about — a size, a colour, an offset. The CurvedAnimation applies easing so motion does not look robotic. The transition widgets — ScaleTransition, FadeTransition, SlideTransition, RotationTransition — rebuild efficiently without a setState on every frame.
AnimatedBuilder: animate anything
When no ready-made transition widget fits, AnimatedBuilder rebuilds just its builder each frame while keeping an expensive child out of the rebuild.
AnimatedBuilder(
animation: _controller,
child: const FlutterLogo(size: 80),
builder: (context, child) => Transform.rotate(
angle: _controller.value * 2 * 3.1415927,
child: child,
),
)
Staggered animations: choreography from one controller
A staggered animation runs several tweens on one controller, each over a different slice of the timeline using Interval. The result is a sequence — fade in, then slide up, then scale — that stays perfectly in sync because it shares a single clock.
// Inside initState, with one _controller of duration 900ms
final _fade = CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.4, curve: Curves.easeIn),
);
final _slide = Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.7, curve: Curves.easeOut),
));
final _scale = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.6, 1.0, curve: Curves.easeOutBack),
),
);
// In build
FadeTransition(
opacity: _fade,
child: SlideTransition(
position: _slide,
child: ScaleTransition(scale: _scale, child: card),
),
)
For staggering a list of items — each row entering slightly after the previous — a small package such as flutter_staggered_animations wraps this pattern, but the principle above is all it does under the hood.

Build real animated screens
The Complete Flutter Guide takes these techniques into full apps — onboarding flows, transitions, and micro-interactions.
Enrol nowCustom page-route transitions
The default page transition is platform-appropriate, but you can supply your own with PageRouteBuilder. Its transitionsBuilder gives you the route's animation, which you drive into any transition widget — here, a slide combined with a fade.
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: const Duration(milliseconds: 400),
pageBuilder: (context, animation, secondaryAnimation) => const DetailScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final curved = CurvedAnimation(parent: animation, curve: Curves.easeOutCubic);
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.15),
end: Offset.zero,
).animate(curved),
child: FadeTransition(opacity: curved, child: child),
);
},
));
CustomPaint: drawing your own animated graphics
When you need something the widget set cannot express — a progress arc, a waveform, a hand-drawn loader — CustomPaint with a CustomPainter lets you draw on a canvas, and feeding it an Animation via repaint animates it each frame.
class ArcPainter extends CustomPainter {
ArcPainter(this.progress) : super(repaint: progress);
final Animation<double> progress;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.deepPurple
..strokeWidth = 8
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
final rect = Offset.zero & size;
canvas.drawArc(rect, -1.57, 6.283 * progress.value, false, paint);
}
@override
bool shouldRepaint(ArcPainter oldDelegate) => false;
}
Because the painter passes the animation to super(repaint:), Flutter repaints only the canvas each frame — not the surrounding widget tree.
Performance: keep animations at 60–120fps
- Animate with transition widgets, not
setStateper frame.FadeTransitionand friends, plusAnimatedBuilder, rebuild only what moves. - Pass a
childtoAnimatedBuilderso the expensive subtree is built once and reused every frame. - Always
disposecontrollers to stop their ticker and avoid leaks and "setState after dispose" errors. - Prefer
Transformand opacity over animating layout properties; moving and fading are cheaper than re-laying-out. - Respect reduced-motion. Check
MediaQuery.of(context).disableAnimationsand shorten or skip non-essential motion. - Profile in profile mode with DevTools — see the Flutter Performance guide for the workflow.
Which animation tool should I use?
- One property changing →
AnimatedContainer/AnimatedOpacityand the rest of the implicit family. - Swapping content →
AnimatedSwitcher. - Same element across screens →
Hero. - Looping, reversing, or gesture-driven →
AnimationController+Tween+ a transition widget. - A sequence of timed steps → one controller with
Intervalcurves (staggered). - A bespoke screen transition →
PageRouteBuilder. - Custom drawn graphics →
CustomPaintdriven by anAnimation.
Frequently asked questions
What is the difference between implicit and explicit animations?
Implicit animations (the Animated* widgets) animate automatically when a property changes — no controller. Explicit animations use an AnimationController you start, reverse, and repeat yourself, for loops, gestures, and choreography.
When do I need an AnimationController?
When you need to control timing directly — loop, reverse, coordinate several tweens, or drive motion from a gesture. The State must use SingleTickerProviderStateMixin and you must dispose the controller.
What is a Tween?
A Tween defines a begin and end value that an animation interpolates across as it moves from 0.0 to 1.0. Combine it with a controller via tween.animate(controller), usually wrapped in a CurvedAnimation for easing.
How do Hero animations work?
Wrap a widget on two screens in a Hero with the same tag. On navigation, Flutter flies the widget from its old position and size to the new one, creating a shared-element transition.
Further reads
Keep going with the tutorials that pair with this guide:
- Flutter Development Guide 2026 — the full Flutter hub.
- Flutter Scrolling and Slivers: The Complete Guide — animate items inside lists and collapsing headers.
- Flutter Layout Widgets: The Complete Guide — the layout the transitions move through.
- Flutter Stack and Positioned — layer widgets and animate them with AnimatedPositioned.
- Flutter Cupertino Widgets: Build iOS-Style UIs — iOS-native transitions and controls.
- Dart Language for Flutter — the language behind every builder and callback.
- Flutter Performance in 2026 — profile animations and hold a smooth frame rate.
Sources: Flutter documentation — Introduction to animations, Implicit and Explicit animation tutorials, Hero animations, and Staggered animations (docs.flutter.dev); the animation and scheduler API references. Verified against current stable Flutter.