Animations in Flutter: From AnimatedContainer to AnimationController

Good animation brings an app to life, but it has to be built the right way. This article covers implicit and explicit animations, Hero transitions and the performance tips that matter, with code.

3 min read1 views
Animations in Flutter: From AnimatedContainer to AnimationController

Good animations make an app feel alive, but badly implemented ones slow it down. Flutter has two families of animation, and choosing the right one is half the work.

#1. Implicit animations, the simplest route

Just provide the new value; Flutter animates the path from the old value to the new. Widgets such as AnimatedContainer, AnimatedOpacity, AnimatedAlign and AnimatedSwitcher:

AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  curve: Curves.easeOut,
  width: expanded ? 240 : 120,
  height: 120,
  decoration: BoxDecoration(
    color: expanded ? Colors.indigo : Colors.teal,
    borderRadius: BorderRadius.circular(expanded ? 32 : 12),
  ),
)

Every setState that changes expanded produces a smooth transition. In about 80% of cases this is all you need.

#2. TweenAnimationBuilder

When no ready-made implicit widget fits:

TweenAnimationBuilder<double>(
  tween: Tween(begin: 0, end: progress),
  duration: const Duration(milliseconds: 600),
  builder: (context, value, _) => LinearProgressIndicator(value: value),
)

#3. Explicit animation with AnimationController

When you want full control (start, stop, repeat, reverse):

class _PulseState extends State<Pulse> with SingleTickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 1),
  )..repeat(reverse: true);

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ScaleTransition(
      scale: Tween(begin: 0.9, end: 1.1).animate(_controller),
      child: const FlutterLogo(size: 80),
    );
  }
}

The golden rule: always dispose the controller.

#4. Hero, transitions between screens

If two widgets on two screens share the same tag, Flutter turns one into the other (a flight) during navigation:

Hero(tag: 'product-$id', child: Image.network(url))

#Performance tips

  • Use AnimatedBuilder with its child parameter so the static part of the tree is not rebuilt every frame.
  • Isolate heavy, static parts with RepaintBoundary.
  • Avoid Opacity on a large subtree; FadeTransition or AnimatedOpacity is cheaper.
  • Animations that only change transform or opacity are the cheapest; avoid changing size and layout every frame.

#A few design principles

  • 200 to 400 ms suits most changes.
  • Use natural curves (Curves.easeOut, easeInOut), not linear.
  • Animation should carry meaning: tell the user what just happened, not just decorate.

If a user enabled "reduce motion" in their device settings, respect it with MediaQuery.disableAnimationsOf(context).

Learn Flutter the professional, project-based way

The FastFlutter VIP course takes you from zero to a published app, with direct support from the instructor.

View the VIP course