In Flutter, what does FrictionSimulation model, and how would you use it to let a thrown flash card coast off-screen?
answer
- slows down, no target
- velocity times drag to the t
- smaller drag stops sooner
- finalX predicts the resting point
- done when speed under tolerance
basics
~20 sFrictionSimulation 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.
solid answer
~40 s`FrictionSimulation(drag, position, velocity)` gives a motion that keeps going and slows down; the velocity after `t` seconds is `velocity * pow(drag, t)`, so a drag below 1 is a per-second retention factor and a smaller value stops sooner. `finalX` returns the resting position before anything moves, which is how you decide at release whether a flash card is dismissed or springs home. To play it, keep the offset in pixels on an `AnimationController.unbounded` and call `animateWith`; the run ends when `isDone` finds the speed below `tolerance.velocity`. `FrictionSimulation.through` solves the drag for a given start and end, and `BoundedFrictionSimulation` clamps to a range.
code
dart · 27 linesimport 'package:flutter/physics.dart';
/// Decide whether a released flash card is thrown off-screen or springs home.
Simulation releaseSimulation({
required double offsetPx, // horizontal offset at release
required double velocityPx, // DragEndDetails.primaryVelocity
required double screenWidth,
}) {
final coast = FrictionSimulation(0.05, offsetPx, velocityPx);
if (coast.finalX.abs() > screenWidth * 0.6) {
// Would coast past 60% of the width: let it glide away.
return coast;
}
return SpringSimulation(
SpringDescription.withDurationAndBounce(bounce: 0.2),
offsetPx,
0,
velocityPx,
snapToEnd: true,
);
}
void main() {
final sim = FrictionSimulation(0.05, 0, 1500);
print(sim.finalX.toStringAsFixed(0)); // about 501 px
print(sim.isDone(0)); // false
}go deeper
Know that FrictionSimulation is for motion that slows to a stop on its own, such as a thrown card, while a spring returns to a target.
Explain the drag formula, why a smaller drag stops sooner, what finalX gives you, and why the controller must be unbounded.
Use finalX to decide dismissal at release time, tune the tolerance for pixel units, and combine friction and spring simulations in one gesture flow.
Judge when hand-built simulations are worth owning versus reusing scroll or sheet widgets that already encapsulate the physics.
## What `FrictionSimulation` models `FrictionSimulation` (in `package:flutter/physics.dart`) models a particle sliding through a thick fluid: it starts at a position with a velocity and slows down on its own, with no target to reach. That makes it the natural partner of a spring. A spring answers "go home", friction answers "keep going and slow down" — the motion of a card the learner has thrown away. Its constructor is: ```dart FrictionSimulation( double drag, double position, double velocity, { Tolerance tolerance = Tolerance.defaultTolerance, double constantDeceleration = 0, }) ``` ## The mathematics in one line With no constant deceleration, the velocity at time `t` seconds is `velocity * pow(drag, t)`. The **drag coefficient** is therefore a per-second retention factor: - `drag` of 0.5 keeps half the speed after one second; 0.05 keeps 5 %. - A **smaller** drag stops the particle **sooner** and closer to where it started. - The coefficient must be below 1 for the particle to slow down; the formulas divide by `log(drag)`. - Position integrates that velocity, so it approaches a limit rather than a hard stop. With `constantDeceleration` above zero, the source additionally subtracts a linear term and computes a finite stop time; a code comment notes this exists for desktop scrolling. ## Useful members | Member | What it tells you | |---|---| | `x(t)` / `dx(t)` | position and velocity after `t` seconds | | `finalX` | where the particle comes to rest — known before the animation starts | | `timeAtX(x)` | when it passes a given position, or `double.infinity` if it never does | | `isDone(t)` | true once the speed drops below `tolerance.velocity` | | `FrictionSimulation.through(...)` | factory that solves for the drag so the particle passes from a start to an end position, finishing at a given end velocity | | `BoundedFrictionSimulation` | subclass that clamps position to a min and max and is also done at either edge | `finalX` is the interview favourite: it lets you **decide the outcome of a throw at release time**. If the resting point lies beyond a dismissal threshold, let the card coast off; otherwise hand the same position and velocity to a `SpringSimulation` and bring it home. ## Driving it from a controller 1. Keep the card offset in pixels in an `AnimationController.unbounded`, because a friction simulation in pixels leaves 0..1 immediately and a bounded controller would clamp it. 2. On release, build the simulation with the current offset and `DragEndDetails.primaryVelocity` (or the relevant component of `velocity.pixelsPerSecond`). 3. Call `controller.animateWith(simulation)`; the controller ticks it until `isDone`. 4. When the returned `TickerFuture` completes after a dismissal, remove the card and reset the offset for the next one. ## Pitfalls - **Units must match.** Position and velocity are in the same length unit; mixing Alignment units with pixels per second gives absurd distances. - **A tolerance too tight for pixels.** The default velocity tolerance is 0.001 per second, so the tail can run long after the card is visually still. Pass a `Tolerance(velocity: ...)` suited to pixels, or use `through`, whose end velocity becomes the tolerance. - **`isDone` is about speed, not distance.** A plain `FrictionSimulation` finishes when it is slow enough, not when it reaches `finalX` exactly. - **Direction matters for `finalX`.** A throw to the left has a negative velocity, so `finalX` is negative; compare its absolute value with the threshold, as the example does. - **Combine, do not chain blindly.** After a friction run ends short of the edge, start a spring from the simulation's final position and zero velocity rather than jumping the card. - **Scrolling is a different API.** Scroll views wrap their own friction-style simulations inside `ScrollPhysics`; configuring those belongs to scroll physics, not to a hand-built `FrictionSimulation`.
- When would you use FrictionSimulation.through instead of the main constructor?When you know where the motion must pass and how fast it should be going there, but not the drag. `through(startPosition, endPosition, startVelocity, endVelocity)` solves the drag coefficient and sets the velocity tolerance to the end velocity, so the simulation reports done as it reaches the end position at that speed.
saying these in an interview costs you the question
- A larger drag coefficient makes the card stop sooner.
- FrictionSimulation needs an end position like a spring does.
- The simulation ends exactly when the card reaches finalX.
- A default 0..1 controller can play a friction simulation in pixels.