skip to content

Animation System

Flutter's motion toolkit: implicitly animated widgets, AnimationController with tweens and curves, transition builders, Hero flights and springs. Interviewers ask which one an effect needs.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

30

In Flutter, why is calling setState from an AnimationController listener on every tick discouraged, and what replaces it?

level: juniorimportance: must knowfreq 52%

answer

  1. whole build method, every frame
  2. listener plus setState
  3. push the listening down
  4. AnimatedBuilder or a FooTransition
  5. FadeTransition skips build entirely

basics

~20 s

setState in a controller listener re-runs the State's whole build method every frame, rebuilding widgets that never change. AnimatedBuilder or a FooTransition widget scopes the rebuild to what depends on the animation, and FadeTransition avoids widget rebuilds altogether.

solid answer

~40 s

The pattern `_controller.addListener(() => setState(() {}))` works, but it marks the whole `State` dirty on every tick, so its entire `build` runs once per frame - headers, text, lists - although only one value moves. The fix is to push the listening down to the widget that needs the value: wrap just the animated part in an `AnimatedBuilder`, or use a ready-made transition such as `RotationTransition`, `ScaleTransition`, `SlideTransition` or `SizeTransition`, each an `AnimatedWidget` that rebuilds only itself. `FadeTransition` goes further: it is a render-object widget whose `RenderAnimatedOpacity` listens to the animation directly, so ticks update the layer's opacity with no widget rebuild at all. `setState` remains right for discrete changes, such as swapping content once the animation completes.

code

dart · 46 lines
dart
import 'package:flutter/material.dart';

// Before: the whole page rebuilds every frame.
// _controller.addListener(() => setState(() {}));
// ... Transform.rotate(angle: _controller.value * 2 * math.pi, child: logo)

// After: only the RotationTransition rebuilds each tick; the page does not.
class SyncPage extends StatefulWidget {
  const SyncPage({super.key});

  @override
  State<SyncPage> createState() => _SyncPageState();
}

class _SyncPageState extends State<SyncPage>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    )..repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Syncing')),
      body: Center(
        child: RotationTransition(
          turns: _controller,
          child: const Icon(Icons.autorenew, size: 48),
        ),
      ),
    );
  }
}

go deeper

for a junior

Recall that setState in a controller listener rebuilds the whole State every frame, and that AnimatedBuilder or a FooTransition widget is the usual replacement.

for a middle

Explain that transition widgets are AnimatedWidgets calling setState on themselves, and that FadeTransition works at the render-object level with no rebuild.

for a senior

Move animation state out of page-level States into small widgets, and read a per-frame rebuild of a whole screen as a design smell rather than tuning around it.

for a principal

Weigh the readability of a single page State against the frame-time cost on low-end devices, and set a convention for where animation controllers may live.

## The listener-plus-setState pattern The first explicit animation most Flutter developers write looks like this: ```dart _controller.addListener(() { setState(() {}); // rebuild with the new _controller.value }); ``` It works: an `AnimationController` notifies its listeners once per frame while it runs, `setState` marks the `State` dirty, and the next frame calls `build`, which reads `_controller.value`. The problem is the **scope**. `setState` knows nothing about which widgets read the value. It schedules the **whole** `build` method of that `State` to run again, every frame, for as long as the animation lasts. ## What it costs - **Build work proportional to the screen, not the effect.** If the `State` owns a page - an app bar, a header, a form, a list - all of it is reconstructed each frame to move one icon. - **Element updates down the tree.** Every new non-`const` widget instance makes its element update and, for stateless and stateful widgets, build again. - **A listener you must manage by hand.** The listener has to be removed or the controller disposed with the `State`; a transition widget manages its own subscription. - **Frame budget pressure.** On a slow device, a large per-frame build is one of the commonest reasons an otherwise simple animation drops frames. ## Scoping the rebuild | Approach | What rebuilds on each tick | |---|---| | `setState` in a controller listener | the entire `build` of that `State` | | `AnimatedBuilder` around the animated part | the widgets its `builder` returns; a pre-built `child` is reused | | a `FooTransition` (`RotationTransition`, `ScaleTransition`, `SlideTransition`, `SizeTransition`) | only that transition widget, which wraps its `child` in a `Transform`, `FractionalTranslation` or `Align` | | `FadeTransition` | no widget at all - its render object listens and updates the layer's opacity | The transition widgets are subclasses of **`AnimatedWidget`**: a `StatefulWidget` whose private `State` subscribes to the animation and calls `setState` on itself. So they still use `setState` - just on the smallest possible widget. `FadeTransition` is the exception: it is a `SingleChildRenderObjectWidget` that hands the `Animation<double>` to a `RenderAnimatedOpacity`, which listens directly and updates only its compositing layer when the alpha changes. ## Before and after: a loading logo on a busy page 1. **Before:** the page `State` owns the controller, adds a listener that calls `setState`, and its `build` returns the whole page including `Transform.rotate(angle: _controller.value * 2 * math.pi, child: logo)`. 2. **After:** the page's `build` returns `RotationTransition(turns: _controller, child: logo)` in the same place, and the listener is deleted. The page's `build` now runs only when the page's own state changes. 3. **Better still for reuse:** move the controller and the transition into a small `LoadingLogo` widget with its own `State`, so the page no longer owns animation state at all. ## When setState is still the right tool - Reacting **once** to a status change - for example, showing a *Continue* button when the controller reaches `AnimationStatus.completed`. - Changing **which** animation or content is shown, rather than the animated value itself. - Very small `State`s whose whole `build` is the animated widget anyway - though a transition is still clearer. The rule interviewers look for: listen to an animation **as low in the tree as possible**, and let the framework's transition widgets do the listening for you. ## Mistakes interviewers listen for - Saying `setState` rebuilds only the widgets that read `controller.value` - it has no such knowledge. - Claiming the transition widgets avoid `setState` altogether; most of them use it, just on a tiny `State`. - Calling `FadeTransition` an `AnimatedWidget`; it works one level lower, on the render object. - Keeping the page-level listener *and* adding a transition, so the page still rebuilds every frame. - Forgetting that the controller still needs disposing whichever listening style is used.

  • The transition widgets also call setState internally. Why is that acceptable?
    Because the `setState` is on the transition's own tiny `State`, whose `build` returns one wrapper such as a `Transform` around a `child` that was built elsewhere. The rebuild is a couple of widgets rather than the page. `AnimatedWidget` also removes its listener in `dispose`, so there is no manual bookkeeping.
  • Why does FadeTransition not rebuild any widget per tick?
    `FadeTransition` is a `SingleChildRenderObjectWidget`, not an `AnimatedWidget`. It passes the `Animation<double>` to a `RenderAnimatedOpacity`, which subscribes to it directly and, when the alpha changes, updates its compositing layer. No build phase runs for the tick at all.

saying these in an interview costs you the question

  • setState rebuilds only the widgets that read controller.value.
  • An AnimationController repaints the screen by itself, so no rebuild is needed.
  • FadeTransition is an AnimatedWidget that rebuilds itself every tick.
  • Transition widgets avoid setState entirely, so they cost nothing per tick.
  • setState should never be used anywhere near an animation.
open as a page

In a Flutter StatefulWidget, how do you create, start, stop and dispose an AnimationController for a pulsing record button?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Mix a ticker provider into the State, create the controller in initState with vsync: this and a duration, call repeat(reverse: true) while recording, stop or animate it back when recording ends, and dispose it in dispose before super.dispose().

open as a page

In Flutter, how does a Hero animation carry a widget between two routes, and what must the two Hero widgets share?

level: juniorimportance: must knowfreq 55%

basics

~20 s

The app's HeroController pairs Hero widgets with equal tags on the outgoing and incoming PageRoutes. During the route transition, the destination hero's child flies in the Navigator's overlay between their bounds while placeholders fill both original slots.

open as a page

In Flutter, how does AnimatedContainer animate a property change, and when do you need an AnimationController instead of an implicit widget?

level: juniorimportance: must knowfreq 65%

basics

~20 s

AnimatedContainer's State keeps its own AnimationController; when a rebuild passes a new property value it tweens from the current value to it over duration and curve. Loops, on-demand playback, gestures and shared timelines need an explicit controller.

open as a page

In Flutter's animation library, what does a Tween do, and how does Tween.animate() relate to Animation.drive()?

level: juniorimportance: must knowfreq 62%

basics

~10 s

A Tween is an Animatable that maps a 0.0-to-1.0 animation value onto a begin-to-end range of sizes, colors or offsets. tween.animate(controller) and controller.drive(tween) are the same operation written from opposite ends.

open as a page

In Flutter, what does AnimatedBuilder's child parameter save when a loading logo rotates every frame, and how does it work?

level: middleimportance: must knowfreq 58%

basics

~20 s

AnimatedBuilder reruns its builder on every tick. A subtree passed as child is built once by the enclosing build and handed back each time, so only the animated wrapper such as Transform.rotate is recreated per frame while the logo subtree is reused.

open as a page

In Flutter, what does the vsync argument of an AnimationController do, and when do you use SingleTickerProviderStateMixin versus TickerProviderStateMixin?

level: middleimportance: must knowfreq 70%

basics

~20 s

vsync is the TickerProvider that creates the controller's Ticker, which calls it once per frame and can be muted by TickerMode. SingleTickerProviderStateMixin vends exactly one ticker per State; use TickerProviderStateMixin for several controllers or a replaced one.

open as a page

In a Flutter app, when would you play a Lottie or Rive file instead of coding the animation, and how do you control each?

level: juniorimportance: should knowfreq 42%

basics

~20 s

Use files for designer-authored illustration: the lottie package plays After Effects JSON as a timeline you can drive with an AnimationController, while the rive package runs .riv state machines that react to inputs or data binding at runtime.

open as a page

In Flutter, how do you write a reusable AnimatedWidget subclass, and when is it preferable to an inline AnimatedBuilder?

level: middleimportance: should knowfreq 32%

basics

~20 s

Extend AnimatedWidget, pass the animation to super as listenable, expose a typed getter and override build. Its hidden State listens and rebuilds only this widget. Prefer it for a named, reusable animated widget; use AnimatedBuilder for one-off inline effects.

open as a page

In Flutter, what do FadeTransition, ScaleTransition, SlideTransition and SizeTransition each take, and how do their animated values translate on screen?

level: middleimportance: should knowfreq 40%

basics

~20 s

FadeTransition takes an opacity, ScaleTransition a scale factor and SizeTransition a size factor, all Animation<double>. SlideTransition takes an Animation<Offset> measured in multiples of the child's own size. Only SizeTransition changes layout size, so only it moves neighbouring widgets.

open as a page

In Flutter, how do an AnimationController's forward, reverse, animateTo and repeat differ, and how long does forward take from a mid value?

level: middleimportance: should knowfreq 45%

basics

~20 s

forward runs to upperBound and reverse to lowerBound; animateTo and animateBack run to any target with an optional curve; repeat loops. Without an explicit duration, the run time scales with the distance left, so forward from 0.5 takes half of duration.

open as a page

In Flutter, how do you run code when an AnimationController finishes, and why might a status listener or an awaited TickerFuture never fire?

level: middleimportance: should knowfreq 40%

basics

~20 s

Add a status listener and check for completed or dismissed, or use the TickerFuture a run returns. repeat() never completes, and stop() or dispose() cancels a run, so its future never resolves; use orCancel or whenCompleteOrCancel to handle both outcomes.

open as a page

In Flutter, what triggers the 'multiple heroes that share the same tag within a subtree' error, and how do tag design or HeroMode fix it?

level: middleimportance: should knowfreq 30%

basics

~20 s

Two Heroes with equal tags on one route trigger it in debug builds, when a navigation starts and the HeroController collects that route's heroes. Make tags unique per slot, such as a section prefix, or wrap one copy in HeroMode(enabled: false).

open as a page

In Flutter, what do a Hero's flightShuttleBuilder, placeholderBuilder and createRectTween each customise during a hero flight?

level: middleimportance: should knowfreq 30%

basics

~10 s

flightShuttleBuilder chooses the widget drawn in flight, placeholderBuilder chooses what stays in each hero's slot while it flies, and createRectTween chooses the path its rectangle follows. Hero.curve, since Flutter 3.44, sets the timing.

open as a page

In Flutter, how would you animate an FAQ card expanding to reveal its answer, and how does AnimatedCrossFade handle the size change?

level: middleimportance: should knowfreq 30%

basics

~20 s

Use AnimatedCrossFade between a collapsed and an expanded child, or wrap the answer in AnimatedSize. AnimatedCrossFade fades the two and animates its own size between them through an internal AnimatedSize, clipping by default, while the hidden child ignores input.

open as a page

Why does Flutter's AnimatedSwitcher sometimes not animate when its child changes, and how do keys fix it?

level: middleimportance: should knowfreq 45%

basics

~20 s

AnimatedSwitcher only transitions when the new child cannot update the old one: a different runtimeType or key. Text('1') to Text('2') updates in place; a ValueKey of the displayed value makes each value a new child.

open as a page

In Flutter, what does TweenAnimationBuilder add over the built-in implicit widgets, and what happens when its tween's end changes mid-animation?

level: middleimportance: should knowfreq 35%

basics

~20 s

TweenAnimationBuilder animates any value a Tween describes and passes it to a builder, with no dedicated widget needed. It runs begin to end on first build; a later new end animates from the current value.

open as a page

In Flutter, how do you make a released flash card spring back to centre using the DragEndDetails velocity and AnimationController.animateWith?

level: middleimportance: should knowfreq 38%

basics

~20 s

Build a SpringSimulation from a SpringDescription, the start and rest positions, and the DragEndDetails velocity converted into controller units, then pass it to AnimationController.animateWith, which ticks the simulation until it settles and ignores the controller's duration.

open as a page

In Flutter, how do SpringDescription's default constructor, withDampingRatio and withDurationAndBounce differ, and what does each parameter do to the motion?

level: middleimportance: should knowfreq 26%

basics

~20 s

The const constructor takes mass, stiffness and damping coefficient; withDampingRatio derives damping from a ratio defaulting to 1.0; withDurationAndBounce (Flutter 3.32+) derives everything from a perceptual duration and a bounce, with mass fixed at 1.

open as a page

In Flutter, when do you use CurvedAnimation rather than CurveTween, and what does CurvedAnimation's reverseCurve change?

level: middleimportance: should knowfreq 46%

basics

~20 s

CurveTween is a stateless Animatable you chain into a pipeline and never dispose. CurvedAnimation is an Animation that listens to its parent's status so it can use a separate reverseCurve on the way back, which is why it must be disposed.

open as a page

In Flutter, how do TweenSequence and Animatable.chain let one controller drive a multi-stage value such as grow, hold, then shrink?

level: middleimportance: should knowfreq 27%

basics

~10 s

TweenSequence splits one controller's timeline into consecutive segments, each a TweenSequenceItem whose relative weight sets its share. chain composes Animatables, so tween.chain(CurveTween(...)) curves t first and then interpolates, without allocating an intermediate Animation.

open as a page

When a Flutter dashboard's rotating sync indicator janks on low-end phones and profiling shows the whole page rebuilding every frame, how do you trace and fix it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Find what listens to the controller: usually a page-level setState listener or an AnimatedBuilder wrapped around the whole Scaffold. Move the listening down to the indicator with RotationTransition or a small AnimatedBuilder with a pre-built child, and build animations once in initState.

open as a page

Why does a Flutter AnimationController stop ticking when its screen is covered by another route, and what does TickerMode control?

level: seniorimportance: should knowfreq 25%

basics

~20 s

The Navigator's Overlay wraps routes hidden under an opaque route in TickerMode(enabled: false), and State ticker mixins mute their tickers there. Muting skips callbacks but time still elapses, so the animation jumps ahead when the route is uncovered.

open as a page

In a Flutter furniture store, what do you check when a product thumbnail wrapped in Hero silently stops flying into its detail page?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A hero skips its flight silently when no pair exists at transition start: unequal tags, a destination Hero built only after data loads, a non-PageRoute or zero-duration route, HeroMode(enabled: false), or an iOS back swipe without transitionOnUserGestures.

open as a page

A Flutter implicit animation sometimes jumps straight to its end value instead of animating; what causes that, and how do you diagnose it?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Usually the widget got a new State, which starts at the target: a changing key, a parent that changes type, or a shifted tree position. Nulls on either side, a zero duration, or a value flipped back before rebuild also snap.

open as a page

In a Flutter flash-card app, a spring-back driven by AnimationController.animateWith never overshoots and reports completed at zero; what is wrong and how do you fix it?

level: seniorimportance: should knowfreq 22%

basics

~20 s

animateWith clamps simulated values to the controller's 0..1 bounds and always reports forward then completed; use AnimationController.unbounded for springs that leave the range, and check the value or use animateBackWith instead of waiting for dismissed.

open as a page

When a Flutter onboarding screen brings in three illustrations one after another from a single AnimationController, how do you stagger them with Interval, and what goes wrong?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Give each illustration its own tween driven through Interval(begin, end, curve:), whose fractions of the controller's duration decide when it moves. Before begin it reads 0.0 and after end 1.0, so one controller plays, reverses and disposes all three together.

open as a page

In Flutter, what does FrictionSimulation model, and how would you use it to let a thrown flash card coast off-screen?

level: middleimportance: nice to knowfreq 14%

basics

~20 s

FrictionSimulation models a particle slowing under fluid drag: velocity decays as velocity × drag^t with no target. Use finalX to predict where a thrown card stops, then animateWith it on an unbounded controller if that point clears the dismissal threshold.

open as a page

In Flutter, which Curves presets overshoot the 0.0-to-1.0 range, and what happens to the tweens and widgets they drive?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Curves.easeInBack, easeOutBack, easeInOutBack and the elastic curves leave 0.0-1.0 between fixed endpoints. Tweens then extrapolate past begin or end: fine for scale or offset, but the Opacity widget, a downstream curve and TweenSequence assert on it.

open as a page

In Flutter, why do Heroes pushed inside a nested Navigator not fly, and how do HeroController and HeroControllerScope fix it?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Hero flights are started by a HeroController observing one Navigator. MaterialApp's HeroControllerScope is claimed by the root Navigator, which bars Navigators below it, so a nested Navigator has none. Give it its own controller through HeroControllerScope or observers.

open as a page