skip to content

Physics-Based Motion

SpringSimulation and FrictionSimulation drive a controller through animateWith, so motion follows velocity instead of a fixed duration. Interviewers ask how a dragged card springs back after a fling.

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

explore

questions

5

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%

answer

  1. illustration, not layout
  2. Lottie is a timeline, Rive a state machine
  3. animate and repeat default true
  4. composition.duration in onLoaded
  5. RiveWidget with RiveWidgetController

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.

solid answer

~30 s

When motion is illustration a designer iterates on, ship a file instead of hand-coded tweens. `Lottie.asset('assets/x.json')` loads an After Effects export and, with no controller, loops because `animate` and `repeat` default to true; for control, pass an `AnimationController`, whose value maps to progress, and set `controller.duration = composition.duration` in `onLoaded` before `forward()`. Rive files hold artboards with state machines: in rive 0.14 and later you call `RiveNative.init()` at startup, load a `File` (for example through `RiveWidgetBuilder` and `FileLoader.fromAsset`), render `RiveWidget(controller: ...)`, and drive the state machine through data binding or inputs. Pick Lottie for timelines, Rive for interactive graphics.

code

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

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

  @override
  State<DeckCompleteBadge> createState() => _DeckCompleteBadgeState();
}

class _DeckCompleteBadgeState extends State<DeckCompleteBadge>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(vsync: this);

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

  @override
  Widget build(BuildContext context) {
    return Lottie.asset(
      'assets/deck_complete.json',
      controller: _controller,
      width: 160,
      onLoaded: (composition) {
        // The controller has no duration until the file is parsed.
        _controller
          ..duration = composition.duration
          ..forward();
      },
    );
  }
}

go deeper

for a junior

Know that Lottie plays After Effects JSON and loops by default, that Rive runs interactive state machines, and that both files are declared assets.

for a middle

Explain how a controller drives Lottie progress and why duration comes from onLoaded, and how RiveWidget, its controller and data binding fit together.

for a senior

Judge when a file beats code, watch raster cost and disposal, and handle the rive 0.14 API break when upgrading older integrations.

for a principal

Set a policy for designer-owned motion assets: which tool, review and size budgets, and how interactive graphics stay testable and accessible.

## Why ship an animation file at all Some motion is illustration rather than layout: a confetti burst when a learner finishes a deck, a mascot that reacts to right and wrong answers. Coding that frame by frame with tweens and `CustomPainter` is slow, and designers cannot iterate on it. Two packages play motion authored in dedicated tools: | | `lottie` | `rive` | |---|---|---| | Source tool | After Effects, exported as JSON (Bodymovin) | the Rive editor | | File | `.json`, or a `.zip` with images | `.riv` binary | | Model | a timeline: progress 0 to 1 | artboards with **state machines** that react at runtime | | Implementation | pure Dart port of lottie-android | `rive_native` C++ runtime and renderer (since rive 0.14) | | Control | an `Animation<double>` mapped to progress | state-machine inputs or data binding | Rule of thumb: **Lottie for fire-and-forget or scrubbed timelines, Rive for interactive, stateful graphics.** Both files are assets, so they must be declared in `pubspec.yaml` like any other. ## Playing a Lottie file - `Lottie.asset(path)` (also `Lottie.network`, `Lottie.file`, `Lottie.memory`) loads, parses and caches the composition. - With no controller it plays by itself: `animate` defaults to true and `repeat` to true, so it **loops**; `reverse` (default false) ping-pongs. - For control, pass your own `AnimationController`. The package maps the controller's value to the animation's **progress**, so `forward()`, `reverse()`, `animateTo(0.5)` or even a spring all work. When a controller is supplied, `animate` and `repeat` are ignored. - The controller has no duration until the file is parsed, which is why the idiom sets `controller.duration = composition.duration` inside `onLoaded` before calling `forward()`; `forward()` on a controller without a duration asserts. - `frameRate` defaults to the composition's own frame rate; `renderCache` (`RenderCache.raster` or `RenderCache.drawingCommands`) trades memory for less work on complex files. ## Playing a Rive file 1. Call `await RiveNative.init()` in `main` after `WidgetsFlutterBinding.ensureInitialized()`; the runtime is native. 2. Load the file, for example with `RiveWidgetBuilder(fileLoader: FileLoader.fromAsset('assets/mascot.riv', riveFactory: Factory.rive), builder: ...)`. The builder receives a sealed `RiveState`: `RiveLoading`, `RiveFailed` or `RiveLoaded`. 3. In the loaded state, render `RiveWidget(controller: state.controller)`. The `RiveWidgetController` runs the chosen artboard and state machine. 4. Drive the state machine from Dart. The package's example recommends **data binding** (view-model properties) over the older **inputs** (`stateMachine.number`, `boolean`, `trigger`). 5. Dispose what you own — a `FileLoader` you create holds the loaded file. `Factory.rive` selects Rive's own renderer; `Factory.flutter` draws through Flutter's renderer. The package README suggests trying both, and comparing with `--no-enable-impeller`, when output differs from the editor. ## Version notes - **rive 0.14** replaced the old pure-Dart runtime with one built on `rive_native`; the last release of the legacy runtime was 0.13.20, so older tutorials use a different widget API. The 0.15 line is in pre-release at the time of writing, adding changes such as automatic view-model binding. - **lottie 3.x** is the current major; recent releases throttle automatic playback to the composition frame rate to cut rebuilds. ## Pitfalls - A Lottie with a custom controller that is never disposed leaks its ticker, like any `AnimationController`. - Expecting a Lottie to respond to taps on its parts: it is a timeline, not a state machine; that is Rive's job. - Rebuilding the loader on every `build`: create the `FileLoader` or composition future once in `State` and reuse it, or the file is fetched and parsed again. - Forgetting accessibility: a decorative animation should be excluded from semantics, and a meaningful one needs a label, whichever package draws it. - Treating either file as free: large compositions still cost raster time on every frame they animate.

  • How would you scrub a Lottie animation with a slider?
    Pass an `AnimationController` as `controller` and set `controller.value` from the slider's `onChanged`, in the range 0 to 1. The lottie package maps the controller's value to the composition's progress, so each value shows the matching frame; `animate` and `repeat` are ignored once a controller is supplied.
  • What does the rive package recommend for driving a state machine from Dart?
    Data binding: properties on a view model that the Rive file declares, bound to the `RiveWidgetController` and set from Dart. The older state-machine inputs (`number`, `boolean`, `trigger`) still work, but the package's own example says it strongly recommends data binding for runtime control.

saying these in an interview costs you the question

  • A Lottie file can react to taps on individual shapes like a state machine.
  • Lottie.asset plays once and stops unless you pass a controller.
  • An AnimationController passed to Lottie needs no duration before forward().
  • Rive and Lottie files load without declaring them as assets.
  • Rive can only render through Flutter's own renderer.
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 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

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