I'm trying to refactor my code for a pinch/zoom functionality into a custom modifier. However, when I move the code to a custom modifier, the PointerInputScope.detectTransformGestures onGesture lambda (callback) is detecting a second gesture after every gesture. In that second detected gesture, the lambda is returning with zoom == 1f (i.e. no zoom), effectively negating any zoom originally applied in the first detected gesture.

Here is my original code (which works correctly) and my refactored code (which results in the second detected gesture). Only the relevant code is included:

Original code (works as intended):

@Composable
fun ZoomableImage(modifier: Modifier, painter: Painter) {
    val scale = remember { mutableStateOf(1f) }
    val rotationState = remember { mutableStateOf(0f) }
    val offsetX = remember { mutableStateOf(1f) }
    val offsetY = remember { mutableStateOf(1f) }

    Box(
        modifier = Modifier
            .combinedClickable {
                interactionSource = remember { MutableInteractionSource() }
            }
            .pointerInput(Unit) {
                detectTransformGestures { _, pan, zoom, rotation ->
                    scale.value *= zoom
                    if (scale.value > 1) {
                        offsetX.value += pan.x
                        offsetY.value += pan.y
                        rotationState.value += rotation
                    } else {
                        scale.value = 1f
                        offsetX.value = 1f
                        offsetY.value = 1f
                        rotationState.value = 0f
                    }
                }
            }

    ) {
        Image(...)
}

Refactored code to custom zoomable modifier:

@Composable
fun ZoomableImage(modifier: Modifier, painter: Painter) {
    var scale by remember { mutableStateOf(1f) }
    var rotationState by remember { mutableStateOf(0f) }
    var offsetX by remember { mutableStateOf(1f) }
    var offsetY by remember { mutableStateOf(1f) }
    
    Box(
        modifier = Modifier
            .zoomable(
                scale,
                { scale = it },
                offsetX,
                { offsetX = it },
                offsetY,
                { offsetY = it },
                isRotation,
                rotationState,
                { rotationState = it },
            )

    ) {
        Image(...)
    }


fun Modifier.zoomable(
    scale: Float,
    setScale: (Float) -> Unit,
    offsetX: Float,
    setOffsetX: (Float) -> Unit,
    offsetY: Float,
    setOffsetY: (Float) -> Unit,
    rotationState: Float,
    setRotation: (Float) -> Unit,
) = composed {
    combinedClickable {
        interactionSource = remember { MutableInteractionSource() }
    }
    .pointerInput(Unit) {

        detectTransformGestures { _, pan, zoom, rotation ->
            val s = scale * zoom
            setScale(s)
            if (s > 1) {
                val panX = offsetX + pan.x
                setOffsetX(panX)
                val panY = offsetY + pan.y
                setOffsetY(panY)
                val r = rotationState + rotation
                setRotation(r)
            } else { 
                // for some reason, detectTransformGestures callback is always called twice when I zoom the image, resulting in the "if" block being triggered first, immediately followed by the "else" block being triggered 
                setScale(1f)
                setOffsetX(1f)
                setOffsetY(1f)
                setRotation(0f)
            }
        }
    }

For some reason, detectTransformGestures's onGesture callback is always called twice when I zoom the image which results in the following:

The if (s > 1) gets triggered first, which is expected since I zoomed the image and the scale changed. However, this is immediately followed by the else block being triggered, which doesn't make sense.

Why is this happening after the refactor?

0

There are 0 best solutions below