Creating Image Zoom In and Out in Jetpack Compose

Baljinder Maan
4 min readOct 19, 2023

--

In this article, we’ll delve into the exciting world of Jetpack Compose, the modern Android UI toolkit, and explore how to implement a critical yet often overlooked feature: image zooming. I will guide you through the steps to create a dynamic and responsive image viewer that allows users to effortlessly zoom in and out, and even pan when exploring enlarged details.

This tutorial assumes a basic understanding of Jetpack Compose, making it suitable for both Compose newcomers and developers looking to add advanced features to their Compose-based applications. By the end of this guide, you’ll have the knowledge and code snippets necessary to incorporate image zoom functionality into your Android app, improving user engagement and overall user experience.

Getting Started

  1. Add necessary dependencies

Jetpack Compose dependencies

implementation "androidx.compose.ui:ui:1.0.0"
implementation "androidx.activity:activity-compose:1.3.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0"

Glide

implementation "com.github.bumptech.glide:glide:4.12.0"
annotationProcessor "com.github.bumptech.glide:compiler:4.12.0"
implementation "com.github.bumptech.glide:glide-compose:1.0.0" //optional

2. Implementing the Zoom Feature

import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.foundation.Image
import kotlin.math.coerceIn

// Define mutable state variables to keep track of the scale and offset.
var scale by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset(0f, 0f)) }

// Create an Image composable with zooming and panning.
Image(
painter = imagePainter, // Replace 'imagePainter' with your image
contentDescription = null,
modifier = Modifier
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
// Update the scale based on zoom gestures.
scale *= zoom

// Limit the zoom levels within a certain range (optional).
scale = scale.coerceIn(0.5f, 3f)

// Update the offset to implement panning when zoomed.
offset = if (scale == 1f) Offset(0f, 0f) else offset + pan
}
}
.graphicsLayer(
scaleX = scale, scaleY = scale,
translationX = offset.x, translationY = offset.y
)
)

Let’s break down the code:

Zooming Gestures:

The “Zooming Gestures” section of the code deals with detecting and handling gestures like pinching to zoom in and out on an image. In Jetpack Compose, you can use the detectTransformGestures function to capture these gestures. Let's break down how it works:

.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
// Update the scale based on zoom gestures.
scale *= zoom

// Limit the zoom levels within a certain range (optional).
scale = scale.coerceIn(0.5f, 3f)

// Update the offset to implement panning when zoomed.
offset = if (scale == 1f) Offset(0f, 0f) else offset + pan
}
}
  1. .pointerInput(Unit) { ... }: The pointerInput modifier allows you to capture touch events and gestures. In this case, we are interested in gestures for zooming and panning.
  2. detectTransformGestures { _, pan, zoom, _ -> ... }: The detectTransformGestures function is a part of the pointerInput and it takes a lambda that's called when transform gestures are detected. It provides three parameters:
  • The first parameter is not used in this example, so it’s represented by an underscore.
  • pan represents the translation or panning gestures.
  • zoom represents the zooming gesture, which is a multiplier that can be less than 1 (zooming out) or greater than 1 (zooming in).

3. scale *= zoom: This line updates the scale factor based on the detected zoom gesture. If zoom is greater than 1, it zooms in (enlarges the image), and if it's less than 1, it zooms out.

4. scale = scale.coerceIn(0.5f, 3f): This is an optional step to limit the zoom levels. It ensures that the scale remains within a certain range. In this case, it's limited to a minimum of 0.5x and a maximum of 3x.

5. offset = if (scale == 1f) Offset(0f, 0f) else offset + pan: This line updates the offset, which is used to implement panning when zoomed in. If the scale is at its original 1x value, the image is centered (offset is [0,0]). Otherwise, it adds the pan value to the current offset to move the image as you pan.

Updating the Image Scale:

In the code for updating the image scale, we are dynamically adjusting the scale of the image in response to zoom gestures detected through the detectTransformGestures function. Here's how this part of the code works:

scale *= zoom

Limiting Zoom Levels:

scale = scale.coerceIn(0.5f, 3f)

Panning the Zoomed Image:
“Panning the Zoomed Image” refers to the capability of moving the zoomed-in image within the view to explore different parts of the image. This is particularly useful when an image is zoomed in, as it allows users to navigate around the image to see details that might be off-screen. In the code snippet, panning is achieved using the offset variable:

offset = if (scale == 1f) Offset(0f, 0f) else offset + pan

Resetting Zoom:

Button(
onClick = {
scale = 1f
offset = Offset(0f, 0f)
}
) {
Text(text = "Reset Zoom")
}

Happy coding, and may your apps come to life with the magic of Jetpack Compose!

--

--