Gesture and Spring Animations
Natural, physics-based motion.
Gesture and Spring Animations is a free Android Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Interactive, Physical Motion
The most delightful animations respond to the user's finger and feel like real objects, with momentum and bounce. To build these you combine gestures with physics-based animation.
This lesson covers Animatable, springs, fling decay, and wiring them to drag gestures for natural, interactive UI.
Animatable: Low-Level Control
While animate*AsState reacts to state, Animatable gives you imperative control. You hold one in remember and drive it from a coroutine with calls like animateTo or snapTo.
This is the foundation for gesture-driven motion.
@Composable
fun BounceOnClick() {
val scale = remember { Animatable(1f) }
val scope = rememberCoroutineScope()
Box(
Modifier
.graphicsLayer { scaleX = scale.value; scaleY = scale.value }
.clickable {
scope.launch {
scale.animateTo(1.3f, spring())
scale.animateTo(1f, spring())
}
}
)
}Springs: dampingRatio & stiffness
A spring spec is defined by two values:
- dampingRatio - how bouncy. Lower bounces more;
1f(critically damped) doesn't overshoot. - stiffness - how fast. Higher reaches the target quicker.
Compose provides constants like Spring.DampingRatioMediumBouncy and Spring.StiffnessLow so you rarely need raw numbers.
val spec = spring<Float>(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessMedium
)
// later, inside a coroutine:
// offset.animateTo(targetValue = 0f, animationSpec = spec)Detecting Drag Gestures
To make motion interactive you read touch input. Modifier.pointerInput with detectDragGestures reports drag deltas as the user moves their finger.
You accumulate those deltas into an offset and apply it to the composable.
@Composable
fun Draggable() {
var offsetX by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), 0) }
.size(80.dp)
.background(Color.Red)
.pointerInput(Unit) {
detectDragGestures { change, drag ->
change.consume()
offsetX += drag.x
}
}
)
}Snapping Back with a Spring
Combine drag with Animatable for a "snap back" effect. While dragging you snapTo the finger position instantly; when released you animateTo(0f) with a spring so the element bounces home.
@Composable
fun SnapBack() {
val offsetX = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Box(
Modifier
.offset { IntOffset(offsetX.value.roundToInt(), 0) }
.size(80.dp)
.background(Color.Blue)
.pointerInput(Unit) {
detectDragGestures(
onDrag = { change, drag ->
change.consume()
scope.launch { offsetX.snapTo(offsetX.value + drag.x) }
},
onDragEnd = {
scope.launch { offsetX.animateTo(0f, spring()) }
}
)
}
)
}Fling with Velocity
Real flings keep moving after the finger lifts. Track velocity with a VelocityTracker, then hand it to animateDecay so the element coasts to a natural stop using a friction-based DecayAnimationSpec.
val decay = rememberSplineBasedDecay<Float>()
// on drag end, with measured velocity:
// scope.launch {
// offsetX.animateDecay(
// initialVelocity = velocity,
// animationSpec = decay
// )
// }Swipe-to-Dismiss with Anchors
For swipe gestures with fixed resting positions (like swipe-to-dismiss), anchoredDraggable snaps to defined anchors. You declare states and their pixel positions, and Compose handles the spring between them.
enum class DragValue { Start, End }
val state = remember {
AnchoredDraggableState(
initialValue = DragValue.Start,
anchors = DraggableAnchors {
DragValue.Start at 0f
DragValue.End at 300f
}
)
}Infinite & Repeating Animations
For continuous motion like a pulsing dot or loading shimmer, use rememberInfiniteTransition with infiniteRepeatable. Set a RepeatMode of Reverse to ping-pong, or Restart to loop.
@Composable
fun Pulse() {
val transition = rememberInfiniteTransition(label = "pulse")
val scale by transition.animateFloat(
initialValue = 1f,
targetValue = 1.2f,
animationSpec = infiniteRepeatable(
animation = tween(700),
repeatMode = RepeatMode.Reverse
),
label = "scale"
)
Box(Modifier.graphicsLayer { scaleX = scale; scaleY = scale })
}Combining Gesture and Spring
The signature interactive pattern: drag to move, release to settle. Track velocity during the drag, then either fling with animateDecay or settle toward an anchor with a spring, depending on distance or speed.
This is exactly how bottom sheets, carousels, and dismissible cards feel so natural.
// pattern:
// 1) onDrag -> animatable.snapTo(current + delta)
// 2) onDragEnd -> if (fast) animatable.animateDecay(velocity, decay)
// else animatable.animateTo(nearestAnchor, spring())Spring Settling in Pure Kotlin
To feel how a spring settles, here's a minimal pure-Kotlin loop. It applies a restoring force toward the target and damps velocity each step, the same idea Compose runs per frame.
fun main() {
var pos = 0f
var vel = 0f
val target = 200f
val stiffness = 0.2f
val damping = 0.75f
repeat(8) { step ->
vel = (vel + (target - pos) * stiffness) * damping
pos += vel
println("step $step pos=${"%.1f".format(pos)}")
}
}Tap, Press, and Multiple Gestures
Drag isn't the only gesture. pointerInput also offers detectTapGestures for taps, double-taps, and long presses, and detectTransformGestures for pinch-to-zoom and rotation.
Here a long press triggers a spring scale, combining gesture detection with an Animatable.
@Composable
fun LongPressScale() {
val scale = remember { Animatable(1f) }
val scope = rememberCoroutineScope()
Box(
Modifier
.graphicsLayer { scaleX = scale.value; scaleY = scale.value }
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
scope.launch { scale.animateTo(1.4f, spring()) }
},
onPress = {
tryAwaitRelease()
scope.launch { scale.animateTo(1f, spring()) }
}
)
}
)
}Quick Check
You're building a draggable card that should bounce back to its origin when released. Which combination is the idiomatic Compose choice?
Recap: Gestures & Springs
You can now build natural, interactive motion:
Animatablegives imperative, coroutine-driven control viaanimateTo/snapTo.springis tuned withdampingRatio(bounce) andstiffness(speed).pointerInput+detectDragGesturesturn touch into motion;animateDecayadds fling.anchoredDraggablesnaps to fixed positions for swipe interactions.rememberInfiniteTransitiondrives looping effects like pulses and shimmers.
That completes Animations in Jetpack Compose, you can now animate values, visibility, content, and gestures with confidence.
Frequently asked questions
Is the “Gesture and Spring Animations” lesson free?
Yes — the full text of “Gesture and Spring Animations” is free to read here on the web, and the Android Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Android Academy course, upgrade to CoddyKit PRO.
What will I learn in “Gesture and Spring Animations”?
Natural, physics-based motion. You practise Android Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Android Academy?
No prior experience is required. Android Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Gesture and Spring Animations” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Android Academy lesson?
Yes. Every Android Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Why Animations Matter
- animate*AsState Basics
- AnimatedVisibility and Transitions
- Gesture and Spring Animations