gesture_physics_animator π
Turn any widget into a spring-backed, draggable, elastic element β driven by real physics, not a tween that fakes it.

π± Platforms supported
Pure Dart/Flutter β no platform channels, no native code β so it runs anywhere Flutter does:
| Android | iOS | Web | Windows | macOS | Linux |
|---|---|---|---|---|---|
| β | β | β | β | β | β |
β¨ Why not just Tween + Curves.elasticOut? π€
Most "drag and snap back" widgets animate release with a Tween and a
canned curve. That looks fine for a slow drag, but a fast flick feels
disconnected β the curve has no idea how fast you were actually moving, so
the snap-back always starts from a standstill.
GesturePhysicsAnimator seeds a real SpringSimulation (or,
when not snapping back, a FrictionSimulation) with the actual release
velocity from your drag, driven through a raw AnimationController via
animateWith() β never a Tween. The result carries your throw's momentum
straight into the settle animation: overshoot-and-settle, elastic boundary
bouncing, momentum-based throws, all continuous with how you actually moved
your finger. π―
π Features
- π§² Velocity-seeded spring release β no jarring switch to a "fake" curve
- π§± Elastic boundary bouncing with configurable energy loss per bounce
- π― Snap-to-target (dock icons, layout guides, anywhere you want)
- π Optional single-axis locking
- ποΈ Named spring presets (
gentle,snappy,bouncy) plus full control over rawSpringDescriptionvalues - πͺΆ Physics math is fully isolated and unit-tested independent of widgets
π¦ Installation
dependencies:
gesture_physics_animator: ^0.1.0
β‘ Quick start
import 'package:gesture_physics_animator/gesture_physics_animator.dart';
GesturePhysicsAnimator(
spring: SpringPresets.snappy,
child: const FlutterLogo(size: 80),
)
That's it β drag it, flick it, watch it settle. Since it moves its child
with a Transform (no extra layout space reserved), it's usually placed
inside a Stack for free 2-D dragging:
Stack(
children: [
GesturePhysicsAnimator(
spring: SpringPresets.bouncy,
bounds: Rect.fromLTRB(-150, -150, 150, 150),
boundaryDamping: 0.4,
child: const FlutterLogo(size: 80),
),
],
)
ποΈ Physics parameters, in plain terms
SpringDescription has three numbers. You don't need a physics degree to
tune them:
| Parameter | Plain-English meaning |
|---|---|
| stiffness | How hard it pulls back toward rest. Higher = snappier, faster to settle. Think "how stiff is the rubber band." |
| damping | How much the motion is resisted. Higher = less bouncy, fewer (or zero) oscillations before it stops. Think "how much air resistance." |
| mass | How heavy the object feels. Higher = slower to speed up and slower to slow down, for the same stiffness/damping. |
Don't want to hand-tune these? Use a preset: π§ SpringPresets.gentle for a
calm, controlled settle Β· β‘ SpringPresets.snappy (the default) for a
crisp, native-feeling response Β· π SpringPresets.bouncy for a playful,
elastic bounce.
GesturePhysicsAnimator(
spring: SpringPresets.gentle, // or .snappy, .bouncy, or your own SpringDescription(...)
child: myWidget,
)
π§± Bounded, elastic dragging
Give it a bounds rectangle (relative to the widget's own starting
position β Offset.zero is always inside it) and it bounces elastically
off the edges instead of dragging straight through them:
GesturePhysicsAnimator(
bounds: Rect.fromLTRB(-100, -100, 100, 100),
boundaryDamping: 0.5, // 0 = perfectly elastic, 1 = stops dead at the edge
child: myWidget,
)
π― Snapping to a target
By default the widget always springs back to its origin on release
(snapBackToOrigin: true). Point it at a custom rest position with
snapTarget, or set snapBackToOrigin: false to let a throw keep going and
settle wherever momentum carries it β useful for "leave it where you dropped
it" widgets like a draggable FAB.
π§© Presets
DraggableCardβ the common "drag it, let go, it springs back" cardElasticBoundedDraggableβ bounded + bouncy, for a draggable FAB or floating panel that shouldn't leave the screen
DraggableCard(child: myCard);
ElasticBoundedDraggable(
bounds: Rect.fromLTRB(-200, -400, 200, 400),
child: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)),
);
πΌοΈ Example app
The example/ app has three interactive demos:
- Free drag β sliders for stiffness and damping so you can feel the difference live
- Bounded β a card that bounces elastically off the screen edges
- Snap dock β drag and release snaps to the nearest of three dock zones
Run it with:
cd example
flutter run
π§ͺ Testing
The physics core (GestureSpringController, BoundedAxisSimulation) is
plain Dart with zero widget dependencies, so it's tested by driving it
directly with TestVSync and a manually-pumped clock β no widget tree, no
synthesized pointer events. Gesture-level behavior (drag, fling, bounds,
axis lock, interrupted animations) is covered separately with real
WidgetTester.fling()/drag() simulation. See test/.
π License
MIT β see LICENSE.
Libraries
- gesture_physics_animator
- Velocity-seeded spring physics for draggable widgets: real momentum, elastic boundary bouncing, and configurable damping β not tween-based "snap back" animation.