Jetpack Compose 拦截子布局中的捏合/缩放

问题描述 投票:0回答:1

我想要一个包含行列的框,其中填充了更多子项,这些子项接受单击(“CL”)和长按(“LO”)以进行缩放和拖动。使用

pointerInput
detectTransforgestures
我可以根据需要改变子布局。

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

val outer = (1..60).toList().chunked(6)

Box(Modifier
.fillMaxSize()
.pointerInput(Unit) {

    //zoom in/out and move around
    detectTransformGestures { gestureCentroid, gesturePan, gestureZoom, _ ->
        val oldScale = zoom
        val newScale = (zoom * gestureZoom).coerceIn(0.5f..5f)
        offset =
            (offset + gestureCentroid / oldScale) - (gestureCentroid / newScale + gesturePan / oldScale)
        zoom = newScale
    }
}) {

    Box(
        Modifier

            .graphicsLayer {
                translationX = -offset.x * zoom
                translationY = -offset.y * zoom
                scaleX = zoom
                scaleY = zoom
            }
            .background(Color.Cyan)
    ) {

        Column {

            outer.forEach { inner ->

                Row {

                    inner.forEach { tile ->

                        var text by remember {
                            mutableStateOf(tile.toString())
                        }

                        Text(text,
                            Modifier
                                .padding(8.dp)
                                .combinedClickable(
                                    onClick = {
                                        text = "CL"
                                    },
                                    onLongClick = {
                                        text = "LO"
                                    }
                                )
                                .background(Color.Green)
                                .padding(8.dp)
                        )
                        
                    }
                }
            }
        }
    }
}

现在的问题是,可点击的子项(标记为绿色)似乎吞下了点击手势,因此,当尝试捏两根手指时,如果我的手指点击按钮(如波纹所示),我将无法缩小回来蓝色或白色区域。

有什么办法不让可点击的子项消耗这种类型的事件或者拦截它,这样他们甚至不会收到像捏合这样的多点触控事件?

kotlin android-jetpack-compose pinchzoom intercept android-jetpack-compose-gesture
1个回答
6
投票

在jetpack Compose中,默认的PointerEventPass是Main,正如您所看到的在此答案中,手势从后代传播到祖先,而您希望变换手势从祖先传播到后代。

为此,您需要使用 PointerEventPass.Initial。当您触摸按钮时,使用 Final 将不起作用,因为它们会消耗事件。现在,青色背景将允许在按钮使用它们之前进行捏合手势,您也可以在指针数量大于 1 时使用事件,以不将单击或长按设置为

// 🔥Consume touch when multiple fingers down
// This prevents click and long click if your finger touches a
// button while pinch gesture is being invoked
val size = changes.size
if (size>1){
    changes.forEach { it.consume() }
}

结果

您应该用于转换的代码是

suspend fun PointerInputScope.detectTransformGestures(
    panZoomLock: Boolean = false,
    consume: Boolean = true,
    pass: PointerEventPass = PointerEventPass.Main,
    onGestureStart: (PointerInputChange) -> Unit = {},
    onGesture: (
        centroid: Offset,
        pan: Offset,
        zoom: Float,
        rotation: Float,
        mainPointer: PointerInputChange,
        changes: List<PointerInputChange>
    ) -> Unit,
    onGestureEnd: (PointerInputChange) -> Unit = {}
) {
    awaitEachGesture {
        var rotation = 0f
        var zoom = 1f
        var pan = Offset.Zero
        var pastTouchSlop = false
        val touchSlop = viewConfiguration.touchSlop
        var lockedToPanZoom = false

        // Wait for at least one pointer to press down, and set first contact position
        val down: PointerInputChange = awaitFirstDown(
            requireUnconsumed = false,
            pass = pass
        )
        onGestureStart(down)

        var pointer = down
        // Main pointer is the one that is down initially
        var pointerId = down.id

        do {
            val event = awaitPointerEvent(pass = pass)

            // If any position change is consumed from another PointerInputChange
            // or pointer count requirement is not fulfilled
            val canceled =
                event.changes.any { it.isConsumed }

            if (!canceled) {

                // Get pointer that is down, if first pointer is up
                // get another and use it if other pointers are also down
                // event.changes.first() doesn't return same order
                val pointerInputChange =
                    event.changes.firstOrNull { it.id == pointerId }
                        ?: event.changes.first()

                // Next time will check same pointer with this id
                pointerId = pointerInputChange.id
                pointer = pointerInputChange

                val zoomChange = event.calculateZoom()
                val rotationChange = event.calculateRotation()
                val panChange = event.calculatePan()

                if (!pastTouchSlop) {
                    zoom *= zoomChange
                    rotation += rotationChange
                    pan += panChange

                    val centroidSize = event.calculateCentroidSize(useCurrent = false)
                    val zoomMotion = abs(1 - zoom) * centroidSize
                    val rotationMotion =
                        abs(rotation * kotlin.math.PI.toFloat() * centroidSize / 180f)
                    val panMotion = pan.getDistance()

                    if (zoomMotion > touchSlop ||
                        rotationMotion > touchSlop ||
                        panMotion > touchSlop
                    ) {
                        pastTouchSlop = true
                        lockedToPanZoom = panZoomLock && rotationMotion < touchSlop
                    }
                }

                if (pastTouchSlop) {
                    val centroid = event.calculateCentroid(useCurrent = false)
                    val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
                    if (effectiveRotation != 0f ||
                        zoomChange != 1f ||
                        panChange != Offset.Zero
                    ) {
                        onGesture(
                            centroid,
                            panChange,
                            zoomChange,
                            effectiveRotation,
                            pointer,
                            event.changes
                        )
                    }

                    if (consume) {
                        event.changes.forEach {
                            if (it.positionChanged()) {
                                it.consume()
                            }
                        }
                    }
                }
            }
        } while (!canceled && event.changes.any { it.pressed })
        onGestureEnd(pointer)
    }
}

使用方法

@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
private fun TouchComposable() {
    var zoom by remember { mutableStateOf(1f) }
    var offset by remember { mutableStateOf(Offset.Zero) }

    val outer = (1..60).toList().chunked(6)

    Box(
        Modifier
            .fillMaxSize()
            .pointerInput(Unit) {

                //zoom in/out and move around
                detectTransformGestures(
                    pass = PointerEventPass.Initial,
                    onGesture = { gestureCentroid: Offset,
                                  gesturePan: Offset,
                                  gestureZoom: Float,
                                  _,
                                  _,
                                  changes: List<PointerInputChange> ->

                        val oldScale = zoom
                        val newScale = (zoom * gestureZoom).coerceIn(0.5f..5f)
                        offset =
                            (offset + gestureCentroid / oldScale) - (gestureCentroid / newScale + gesturePan / oldScale)
                        zoom = newScale


// 🔥Consume touch when multiple fingers down
// This prevents click and long click if your finger touches a
// button while pinch gesture is being invoked
                        val size = changes.size
                        if (size > 1) {
                            changes.forEach { it.consume() }
                        }
                    }
                )
            }) {

        Box(
            Modifier

                .graphicsLayer {
                    translationX = -offset.x * zoom
                    translationY = -offset.y * zoom
                    scaleX = zoom
                    scaleY = zoom
                }
                .background(Color.Cyan)
        ) {

            Column {

                outer.forEach { inner ->

                    Row {

                        inner.forEach { tile ->

                            var text by remember {
                                mutableStateOf(tile.toString())
                            }

                            Text(text,
                                Modifier
                                    .padding(8.dp)
                                    .combinedClickable(
                                        onClick = {
                                            text = "CL"
                                        },
                                        onLongClick = {
                                            text = "LO"
                                        }
                                    )
                                    .background(Color.Green)
                                    .padding(8.dp)
                            )

                        }
                    }
                }
            }
        }
    }

}

您还可以在这个库中找到这个手势和其他一些手势

https://github.com/SmartToolFactory/Compose-Extended-Gestures

本教程中有更多关于手势的信息

https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials

© www.soinside.com 2019 - 2024. All rights reserved.