gesture_physics_animator 0.1.1
gesture_physics_animator: ^0.1.1 copied to clipboard
Velocity-seeded spring physics for draggable widgets: real momentum, elastic boundary bouncing, and configurable damping — not a tween-based "snap back."
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.