What You Will Build
A zoomable and pannable content area that responds to pinch gestures for scaling and drag gestures for panning. Includes a reset button and a live scale indicator. This is the same interaction used in photo viewers, map views, and document readers.
Why This Pattern Matters
Pinch-to-zoom is fundamental to any media-heavy app. The detectTransformGestures API in Compose handles multi-touch elegantly, giving you pan, zoom, and rotation in a single callback. Mastering it unlocks image galleries, map interactions, and canvas-based drawing tools.
Step 1: State for Scale and Offset
@Composable
fun PinchToZoomScreen() {
var scale by remember { mutableFloatStateOf(1f) }
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
Step 2: Apply Transform Gestures
The detectTransformGestures function provides pan offset, zoom factor, and rotation angle in one callback:
Box(
modifier = Modifier
.size(280.dp, 350.dp)
.graphicsLayer {
scaleX = scale
scaleY = scale
translationX = offsetX
translationY = offsetY
}
.clip(RoundedCornerShape(16.dp))
.background(
Brush.linearGradient(
listOf(Color(0xFF667EEA), Color(0xFF764BA2))
)
)
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
scale = (scale * zoom).coerceIn(0.5f, 4f)
offsetX += pan.x
offsetY += pan.y
}
},
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Zoom Me",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = Color.White)
Text("Scale: ${"%.1f".format(scale)}x",
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.8f))
}
}
Step 3: Add a Reset Button
Button(
onClick = {
scale = 1f
offsetX = 0f
offsetY = 0f
},
modifier = Modifier.padding(16.dp)
) {
Text("Reset")
}
Tips and Pitfalls
- Clamp the scale:
coerceIn(0.5f, 4f)prevents the content from becoming too small or too large. Adjust these bounds for your use case. - Pan limits: In production, calculate max offset based on current scale to prevent the content from being dragged completely off-screen.
- Animated reset: Replace instant reset with
Animatablevalues andanimateTofor a smooth snap-back. - Double-tap to zoom: Add
detectTapGestures(onDoubleTap = { scale = if (scale > 1f) 1f else 2f })alongside the transform gesture.
Min SDK: API 21+ with Compose 1.0+