Skip to content

Animations

Flutter animations split into two families, and picking the right one is most of the skill:

  • Implicit — you change a value, Flutter animates to it automatically. Zero controllers, no lifecycle to manage.
  • Explicit — you drive the animation yourself with an AnimationController. Full control (repeat, reverse, sync several values, respond to gestures), at the cost of a ticker and disposal.

Start implicit; go explicit only when implicit can’t express what you need.

The AnimatedFoo widgets (AnimatedContainer, AnimatedOpacity, AnimatedPadding, …) take a duration and animate whenever their target properties change.

AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 200 : 100, // change this (e.g. in setState)...
height: 100,
color: Colors.blue,
// ...and Flutter smoothly animates from the old width to the new one.
)

TweenAnimationBuilder generalizes this: give it a Tween and a target, and it animates the value and rebuilds your builder — implicit animation for any property.

An AnimationController produces values from 0 to 1 over a duration, ticked by a vsync. A Tween maps 0..1 onto your range, and AnimatedBuilder rebuilds only the animated part.

class _PulseState extends State<Pulse> with SingleTickerProviderStateMixin {
late final AnimationController _c = AnimationController(
vsync: this, // the ticker (why we need the mixin)
duration: const Duration(seconds: 1),
)..repeat(reverse: true);
@override
void dispose() { _c.dispose(); super.dispose(); } // ALWAYS dispose the controller
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _c,
builder: (context, child) => Opacity(opacity: _c.value, child: child),
child: const FlutterLogo(), // built once, not rebuilt each tick
);
}
}
flowchart LR
  ctrl["AnimationController
ticks 0 to 1 via vsync"] --> tween["Tween / Curve
maps 0..1 to your range"]
  tween --> ab["AnimatedBuilder
rebuilds the animated part"]
  ab --> ui["widget updates each tick"]
The explicit animation pipeline

Two rules that prevent the common bugs: the controller needs a vsync (mix in SingleTickerProviderStateMixin), and you must dispose() it — a leaked controller keeps ticking forever. Pass the unchanging part as child so it isn’t rebuilt on every tick.

A Curve (Curves.easeInOut, Curves.elasticOut, …) reshapes linear time into natural motion. Both families take a curve; using one is the difference between a mechanical slide and a motion that feels designed.

What is an implicit animation?
What does an AnimationController need in order to tick?
What happens if you forget to dispose an AnimationController?
When should you choose an explicit animation over an implicit one?