How to constraint an image panning to the edges of the Box in Jetpack Compose?

629 Views Asked by At

How to constraint an image panning to the edges of the Box in Compose?

I'm using pointerInput(Unit) { detectTransformGestures { centroid, pan, zoom, rotation -> }} to control zooming and panning.

I'm solving this panning problem when the image is minimized to 1f with if (scale.value == 1f) 0f else panOffsetX. I want to do the same for the image zoomed-in (1f < scale <= 3f)

Box(
    modifier = Modifier
        .clip(RectangleShape)
        .fillMaxWidth()
        .background(Color.Gray)
        .pointerInput(Unit) {
            detectTransformGestures { centroid, pan, zoom, rotation ->
                val constraintZoom = when {
                    scale.value > 3f -> 3f
                    scale.value < 1f -> 1f
                    else -> (scale.value * zoom)
                }
                scale.value = constraintZoom
                panOffset += pan
                panOffsetX += pan.x
                panOffsetY += pan.y
                centroidOffset = centroid
                rotationState.value += rotation
            }
        }
) {
    Image(
        modifier = Modifier
            .align(Alignment.Center)
            .graphicsLayer(
                scaleX = maxOf(1f, minOf(3f, scale.value)),
                scaleY = maxOf(1f, minOf(3f, scale.value)),
                translationX = if (scale.value == 1f) 0f else panOffsetX,
                translationY = if (scale.value == 1f) 0f else panOffsetY,
            ),
        contentDescription = null,
        painter = painterResource(R.drawable.my_sample_image)
    )
}

1

There are 1 best solutions below

1
On

Trick is to limit it using your Composable size and zoom level

val maxX = (size.width * (zoom - 1) / 2f)
val maxY = (size.height * (zoom - 1) / 2f)

offset = newOffset
offset = Offset(
    newOffset.x.coerceIn(-maxX, maxX),
    newOffset.y.coerceIn(-maxY, maxY)

Full Implementation

States

var zoom by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
var size by remember { mutableStateOf(IntSize.Zero) }

Modifier

  val imageModifier = Modifier
        .fillMaxSize()
        .aspectRatio(4 / 3f)
        .clipToBounds()
        .pointerInput(Unit) {
            detectTransformGestures(
                onGesture = { _, gesturePan, gestureZoom, _ ->
                    
                    zoom = (zoom * gestureZoom).coerceIn(1f, 3f)
                    val newOffset = offset + gesturePan.times(zoom)
                    
                    val maxX = (size.width * (zoom - 1) / 2f)
                    val maxY = (size.height * (zoom - 1) / 2f)

                    offset = Offset(
                        newOffset.x.coerceIn(-maxX, maxX),
                        newOffset.y.coerceIn(-maxY, maxY)
                    )
                }
            )
        }
        .onSizeChanged {
            size = it
        }
        .graphicsLayer {
            translationX = offset.x
            translationY = offset.y
            scaleX = zoom
            scaleY = zoom
        }