Animations
Two families of animation
Section titled “Two families of animation”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.
Implicit: animate to a new value
Section titled “Implicit: animate to a new value”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.
Explicit: drive it yourself
Section titled “Explicit: drive it yourself”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"]
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.
Curves
Section titled “Curves”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.