How to implement zoom and pan on an Image in jetpack compose

3.3k views Asked by At

I have an Image composable where I want the users to able to zoom into a part of the image. For example, if pinched on the bottom left of the image then zoom into that the bottom left area and not the center of the image. And when zoomed in then be able to pan around the image if a single finger.

In my current code, I have the pinch to zoom in & out logic but it defaults to the center of the image no matter where the image was pinched And there is no pan logic to the image when zoomed in.

Image(
    painter = painterResource(id = R.drawable.sample_image),
    contentDescription = "some description here",
    modifier = Modifier
        .graphicsLayer(
            scaleX = scale.value,
            scaleY = scale.value
        )
        .pointerInput(Unit) {
            detectTransformGestures { _, _, zoom, _ ->
                scale.value = when {
                    scale.value < 0.5f -> 0.5f
                    scale.value > 3f -> 3f
                    else -> scale.value * zoom
                }
            }
        }
)

So I want to achieve two things:

  1. be able to zoom where it was actually pinched(not the center of the image)
  2. When Zoomed in, be able to pan around the image

I've tried implementing multiple solutions from other stack overflow answers but they don't seem to work.

3

There are 3 answers

2
uragiristereo On BEST ANSWER

There is a newly released compose library to do that, I haven't tried it personally but it has a good promise.

Go check out Telephoto

[OLD ANSWER]:

There is currently not possible to do that with compose.

However i recommend you to interop with TouchImageView or similiar using AndroidView composable.

0
Gourav Dadhich On

A little change has been made in Roberto Leinardi's answer. I have put a limit in my code so that the image doesn't go out of the container.

@Composable
fun TouchImageView() {
    var zoom by remember { mutableStateOf(1f) }
    var offsetX by remember { mutableStateOf(0f) }
    var offsetY by remember { mutableStateOf(0f) }
    val minScale = 0.5f
    val maxScale = 2f

    Image(
        painter = painterResource(id = R.drawable.info_1),
        contentDescription = "some description here",
        contentScale = ContentScale.Fit,
        modifier = Modifier
            .graphicsLayer(
                scaleX = zoom,
                scaleY = zoom,
                translationX = offsetX,
                translationY = offsetY,
            )
            .pointerInput(Unit) {
                detectTransformGestures(
                    onGesture = { _, pan, gestureZoom, _ ->
                        zoom = (zoom * gestureZoom).coerceIn(minScale, maxScale)
                        if (zoom > 1) {
                            val maxOffsetX = 197.82568f \\chnange this to according to your image size
                            val maxOffsetY = 197.82568f
                            offsetX += pan.x * zoom
                            offsetY += pan.y * zoom
                            offsetX = offsetX.coerceIn(-maxOffsetX, maxOffsetX)
                            offsetY = offsetY.coerceIn(-maxOffsetY, maxOffsetY)
                        } else {
                            offsetX = 0f
                            offsetY = 0f
                        }
                    }
                )
            }
            .fillMaxSize()
    )
}
0
Roberto Leinardi On

A partial solution that allows pan can be achieved like this:

var zoom by remember { mutableStateOf(1f) }
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
val minScale = 0.5f
val maxScale = 3f
Image(
    painter = painterResource(id = R.drawable.sample_image),
    contentDescription = "some description here",
    contentScale = ContentScale.Fit,
    modifier = Modifier
        .graphicsLayer(
            scaleX = zoom,
            scaleY = zoom,
            translationX = offsetX,
            translationY = offsetY,
        )
        .pointerInput(Unit) {
            detectTransformGestures(
                onGesture = { _, pan, gestureZoom, _ ->
                    zoom = (zoom * gestureZoom).coerceIn(minScale, maxScale)
                    if(zoom > 1) {
                        offsetX += pan.x * zoom
                        offsetY += pan.y * zoom
                    }else{
                        offsetX = 0f
                        offsetY = 0f
                    }
                }
            )
        }
        .fillMaxSize()
)

(inspired by ComposeZoomableImage)