Course outline

Flutter / Inclusive interfaces and target evidence / Lesson 14

Animation and resource lifetimes

Select implicit or explicit animation by control requirements and keep motion subordinate to meaning and lifecycle.

About 5 min readContent reviewed
On this page

Lesson goals

  • Animate a meaningful state change
  • Prefer implicit animation when targets are enough
  • Own explicit controllers in State

Animate a meaningful state change

Animation should help a user understand continuity, causality, or changing emphasis. It is not a substitute for the state change itself: a save indicator must still communicate success without relying only on motion or color. Start with the source and destination states and determine whether interruption or reversal is allowed. A decorative effect can remain implicit, while a gesture-driven sequence may need explicit progress control. Keep motion preferences in the design contract and provide a reduced-motion path when appropriate. Test rapid repeated interaction because an animation rarely receives a perfectly isolated single activation in real use.

Prefer implicit animation when targets are enough

Implicit animation widgets manage their own animation machinery while you supply a new target value. They are useful for a size, alignment, or decoration that should transition after state changes. The first fragment changes the container height according to an existing expanded flag. It does not store a second animated height in application state. The framework interpolates presentation toward the current target. If the flag changes again during the transition, test that the resulting motion remains understandable and that content is not hidden from access while still visually interactive.

dart Illustrative Dart or Flutter fragment; requires the declared SDK host.
Widget animatedPreview(bool expanded) => AnimatedContainer(
  duration: const Duration(milliseconds: 180),
  curve: Curves.easeInOut,
  height: expanded ? 160 : 80,
  color: Colors.teal,
  child: const Center(child: Text('Preview')),
);
Expected output
Changing expanded animates the bounded height between 80 and 160 logical pixels.

The duration is an illustrative design choice, not a universal recommendation. The parent must permit both target sizes.

Own explicit controllers in State

An AnimationController exposes progress that can be started, reversed, stopped, or coordinated with other values. It requires a ticker provider and an explicit disposal owner. The second fragment creates its controller in initState and releases it in dispose. SingleTickerProviderStateMixin is suitable for the single-controller lifetime shown; different controller arrangements may require another documented provider choice. Never create a new controller every build or keep one alive merely because a callback still references it. The widgetโ€™s disappearance must end the resources owned by its State.

dart Illustrative Dart or Flutter fragment; requires the declared SDK host.
class _PulseState extends State<Pulse> with SingleTickerProviderStateMixin {
  late final AnimationController controller;
  @override void initState() {
    super.initState();
    controller = AnimationController(vsync: this,
      duration: const Duration(milliseconds: 300));
  }
  @override void dispose() { controller.dispose(); super.dispose(); }
  @override Widget build(BuildContext context) => FadeTransition(
    opacity: controller, child: const Text('Ready'));
}
Expected output
The initial controller value is 0; explicit progress changes drive opacity.

Pulse is the surrounding StatefulWidget. A real interaction must decide when to call forward or reverse; the fragment does not auto-start motion.

Understand visual state and interaction state

Opacity zero does not automatically mean a subtree cannot receive hit tests or focus. When hiding an interactive panel, coordinate visibility with the intended interaction and semantics policy rather than assuming a fade disables it. Conversely, instantly removing a subtree can discard its local draft state when the product expected temporary concealment. Choose whether the panel remains in the tree, maintains state, receives input, and appears in semantics at each transition stage. These are separate decisions that should be tested together during opening, closing, and interrupted animation.

Keep expensive work out of per-frame callbacks

Animation can rebuild or repaint frequently. Move invariant subtrees into stable child parameters when supported, and avoid decoding large payloads or performing data access in an animation builder. Measure whether the bottleneck is Dart work, layout, or rasterization before introducing additional boundaries. An unnecessary offscreen layer or broad repaint can be costly even when the animation controller itself is simple. Use profile evidence on a representative target, because development debug overhead and desktop hardware can obscure the actual experience on the intended device.

Test progression, interruption, and removal

A controlled widget test can pump an initial frame, advance a chosen duration, inspect an intermediate presentation, and complete the transition. Add a reversal before completion and remove the owner while animation is active to exercise cleanup. Do not rely on pumpAndSettle for an intentionally repeating animation, since it may never settle. Verify the task remains usable with motion disabled or reduced under the application policy. A passing frame test establishes the tested treeโ€™s behavior, while actual frame pacing and assistive interaction still require target observation.

Common errors and fixes

A ticker remains active after leaving the screen

Why it happens
The owning State did not dispose its controller.
How to fix it
Dispose the owned controller before completing State disposal.
Prevent it next time
Remove the widget during active motion in a test.

An invisible control still accepts input

Why it happens
Opacity was mistaken for an interaction gate.
How to fix it
Coordinate hit testing, focus, and semantics with the hidden state.
Prevent it next time
Test closing transitions with keyboard and touch.

A repeating animation makes a test time out

Why it happens
The test waits for a state that never settles.
How to fix it
Advance controlled frames and assert a defined interval.
Prevent it next time
Choose timing helpers from the animation contract.

Check your understanding

1. When is an explicit controller justified over AnimatedContainer?

Show worked solution

When the interaction needs direct progress, coordinated sequences, gesture-driven movement, or deliberate start and reverse control. If only a new target size matters, an implicit widget is often simpler. Choose from required behavior and lifecycle complexity rather than assuming explicit code is always more advanced.

2. What should be checked when a hidden panel contains a text field?

Show worked solution

Decide whether its draft survives, whether focus moves somewhere meaningful, whether hidden controls can receive input, and whether semantics exposes the panel. Verify those choices during the transition as well as after it completes; opacity alone answers none of them.

Key takeaways

  • Animate a meaningful state change
  • Test progression, interruption, and removal

Official references