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?