Skip to content
Boring Doc
Boring Doc explanation Verified against Flutter 3.47.7 3 min readanimationsAnimatedContainerAnimatedOpacityTweenAnimationBuilderAnimationController

Animations

Implicit, explicit and physics-based motion.

Reach for an implicit animation first: give AnimatedContainer, AnimatedOpacity or TweenAnimationBuilder a duration and a new target value, and the widget animates there on its own. Use an explicit AnimationController with Tweens and AnimatedBuilder when you must start, stop, reverse or repeat the motion yourself.

  1. Implicit animations (ImplicitlyAnimatedWidget subclasses) own their controller: you change a property such as opacity or width inside setState and the widget interpolates from the old value to the new one over `duration`.
  2. TweenAnimationBuilder is the implicit escape hatch: it animates any value a built-in widget does not cover, by handing the in-between value to a builder.
  3. Explicit animations use an AnimationController (0.0 to 1.0 over a duration, ticking once per frame) that you create in initState with vsync: this from SingleTickerProviderStateMixin, and dispose in dispose().
  4. A Tween maps the controller's 0..1 value to the type you need (double, Color, Rect); a CurvedAnimation or curve argument reshapes the timing with one of the Curves constants.
  5. AnimatedBuilder (or a prebuilt transition such as FadeTransition) rebuilds just the part that depends on animation.value; pass static subtrees through its `child` so they are not rebuilt every frame.
  6. Hero is the shared-element transition: two Hero widgets with the same tag on two routes, and Navigator push/pop flies the widget between them in an overlay.
ComparisonImplicit versus explicit animations
Implicit (ImplicitlyAnimatedWidget)Explicit (AnimationController)
Who owns the controllerThe widget, internallyYour State, via vsync: this
How it startsRebuild with a new target value (usually setState)controller.forward(), reverse(), repeat(), animateTo(), fling()
Control while runningNone: it always animates to the latest targetPause, reverse, repeat, jump to a value, read status
Code you writeduration, curve, the new valueController, Tween, curve, builder/transition, dispose
Typical widgetsAnimatedContainer, AnimatedOpacity, AnimatedPadding, AnimatedPositioned, AnimatedSwitcher, TweenAnimationBuilderAnimatedBuilder, AnimatedWidget, FadeTransition, SlideTransition, ScaleTransition, SizeTransition
Rebuild costRebuilds the implicit widget's subtree each tickRebuilds only the builder; static `child` is reused
Best forState-driven UI changes: toggles, selection colour, layout shiftsLoops, gestures, staggered sequences, anything that must be interrupted

Both end up driving an AnimationController and a Ticker; the difference is who owns the controller and how much control you get.

Thermostat versus a dimmer switch

An implicit animation is a thermostat: you set the target temperature and walk away; the system takes care of the ramp from the current value to the new one, and if you change the target halfway it simply re-aims. An explicit animation is a dimmer switch in your hand: an AnimationController is the knob position (0 to 1), a Tween is the label that says what 0 and 1 mean (dark to bright, small to large), a Curve is how stiff the knob feels through its travel, and AnimatedBuilder is the lamp wired to the knob. You decide when to turn it, how far, and whether to go back.

Where the analogy breaks: A thermostat really is simpler hardware; in Flutter both styles use the same AnimationController and Ticker underneath, so implicit is not cheaper per frame. The dimmer analogy also hides the vsync contract: a controller must be tied to a TickerProvider so it stops consuming frames when the widget is not visible, and it must be disposed or the ticker keeps running after the State is gone.

Minimal working example

Implicit: AnimatedContainer toggled by setState
dart
import 'package:flutter/material.dart';

class ExpandingCard extends StatefulWidget {
  const ExpandingCard({super.key});

  @override
  State<ExpandingCard> createState() => _ExpandingCardState();
}

class _ExpandingCardState extends State<ExpandingCard> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _expanded = !_expanded),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOut,
        width: _expanded ? 240 : 120,
        height: _expanded ? 160 : 80,
        decoration: BoxDecoration(
          color: _expanded ? Colors.indigo : Colors.teal,
          borderRadius: BorderRadius.circular(_expanded ? 24 : 8),
        ),
        child: const Center(child: Text('Tap me')),
      ),
    );
  }
}
Expected: Each tap grows or shrinks the box over 300 ms while the colour cross-fades and the corners round off. Tapping mid-animation re-targets smoothly instead of jumping.Flutter ≥ 3.0.0Animate the properties of a container (cookbook)
  1. L11The only state is the target: which of the two configurations the card should be in.
  2. L16setState triggers a rebuild; AnimatedContainer notices its properties changed and starts animating toward them.
  3. L18duration is required for every implicitly animated widget.
  4. L19curve is optional; Curves.linear is the default. easeInOut, fastOutSlowIn and bounceOut are common choices.
  5. L20Width, height, decoration colour and border radius are all interpolated together over the same duration.

Use cases

  • Reflecting a state change with motion: selected tile colour, expanding a card, showing or hiding a banner (implicit).
  • A custom property with no built-in Animated* widget, such as animating a number counting up or a custom painter parameter (TweenAnimationBuilder).
  • Continuous or interruptible motion: loading pulses, drag-to-dismiss, staggered entrances, anything that must pause, reverse or repeat (explicit).
  • Moving an image or card from a list to a detail page across a route change (Hero).

Benefits

  • Implicit widgets need no controller and no dispose; they are often a two-line change to an existing Container or Opacity.
  • Explicit animations give frame-accurate control and compose: one controller can drive several Tweens and curves at once.
  • Both run on the same Ticker and scheduler, so animations started in the same frame stay in sync.

Limitations

  • Implicit widgets always animate to the latest target; you cannot pause them, run them backwards, or know when they finished without extra work (TweenAnimationBuilder has onEnd).
  • Explicit animations add boilerplate: mixin, initState, dispose, and careful handling in didUpdateWidget.
  • Rebuilding a large subtree each tick (in an implicit widget's child or inside AnimatedBuilder's builder) is the most common animation jank; see the performance topic.
  • Hero requires the same tag on exactly one Hero per route and both heroes to be on screen at push/pop time.

Alternatives and trade-offs

  • Built-in transition widgets (FadeTransition, SlideTransition, ScaleTransition) — You already have an Animation<double> and want the standard effect without writing a builder.
  • AnimatedSwitcher — You replace one child widget with another and want the swap cross-faded; give the children distinct keys.
  • Physics simulations (AnimationController.animateWith, fling) — Motion should follow velocity and springs rather than a fixed duration, for example after a drag.
  • Page route transitions — The thing that animates is the whole screen; see navigation and routing.

Not disposing the AnimationController

Problem: The ticker keeps firing after the State is gone, costing CPU and raising an assertion about a Ticker that was still active when the widget was disposed.

Fix: Create the controller in initState and call controller.dispose() in dispose(), before super.dispose().

✗ Controller never disposed
dart
@override
void initState() {
  super.initState();
  _controller = AnimationController(vsync: this, duration: const Duration(seconds: 1))
    ..repeat();
}
// no dispose override
✓ Disposed with the State
dart
@override
void dispose() {
  _controller.dispose();
  super.dispose();
}

Rebuilding static widgets inside AnimatedBuilder's builder

Problem: Everything returned from builder is rebuilt on every frame of the animation. A heavy subtree in there (an image, a list, a card with text) is rebuilt 60 to 120 times per second even though it does not change.

Fix: Build the static part once and pass it as AnimatedBuilder's `child`; the builder receives it as its second argument and only wraps it with the animated transform.

✗ Heavy child rebuilt every tick
dart
AnimatedBuilder(
  animation: _controller,
  builder: (context, _) => Transform.rotate(
    angle: _controller.value * 6.28,
    child: const ExpensiveCard(), // rebuilt each frame
  ),
)
✓ Static child passed through
dart
AnimatedBuilder(
  animation: _controller,
  child: const ExpensiveCard(), // built once
  builder: (context, child) => Transform.rotate(
    angle: _controller.value * 6.28,
    child: child,
  ),
)

Calling setState from addListener to drive the whole screen

Problem: The tutorial's first example uses addListener + setState to show the mechanism, but in a real page it marks the entire State's subtree dirty every frame, not just the animated widget.

Fix: Wrap only the animated part in AnimatedBuilder, an AnimatedWidget subclass, or a transition widget such as FadeTransition, so each tick rebuilds the smallest possible subtree.

Hero tags that do not match, or duplicate tags on one route

Problem: If the destination Hero's tag differs from the source, nothing flies and the route just fades in. Two Heroes with the same tag in a single route throw an assertion when a transition starts.

Fix: Use the item's identity (id, URL) as the tag on both routes, and in lists make sure each item's tag is unique.

This page is Boring Doc’s independent explanation. For the complete specification use the official pages below (content © The Flutter Authors, CC BY 3.0; code samples BSD). Dates show when the explanation was last checked and when the official page last changed.

  • Introduction to animations Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/ui/animations
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Animations API overview Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/ui/animations/overview
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Animations tutorial Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/ui/animations/tutorial
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Implicit animations Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/ui/animations/implicit-animations
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Hero animations Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/ui/animations/hero-animations
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Animate the properties of a container Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/cookbook/animation/animated-container
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Fade a widget in and out Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/cookbook/animation/opacity-animation
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Simple animations (learning pathway) Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/learn/pathway/tutorial/implicit-animations
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
Was this useful?
AI explanationAI-generated · grounded in cited sources
AI explanations are not configured
Set AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.