Jetpack Compose - 将点击传播到较低的 UI 层

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

我正在努力为我的应用程序创建“入门”体验,这本质上是关于如何使用该应用程序的快速指南。为了实现这一目标,我希望用户单击应用程序指示他们的位置。具体来说,我想创建一个用户界面,其中包含允许用户单击的圆形元素和不允许单击的灰色透明背景(为了清楚起见,请参阅附图)

我当前的挑战在于弄清楚如何将此单击事件传播到较低的 UI 层。

以下是相关代码片段:

        Canvas(
        modifier = Modifier
            .fillMaxSize()
            .zIndex(3f)
            .pointerInput(Unit) {
                detectTapGestures(onTap = { offset ->
                    if (!Rect(position, volume).contains(offset)) {
                        Log.d("app-debugger9","Cancel the click!")
                    }
                    else
                    {
                        Log.d("app-debugger9","Propagate click to below UI layers!")
                    }
                })
            }
    ) {
        val circlePath = Path().apply {
            addOval(Rect(position, volume))
        }
        clipPath(circlePath, clipOp = ClipOp.Difference) {
            drawRect(SolidColor(Color.Black.copy(alpha = alphaValue)))
        }
    }

Log.d(“app-debugger9”,“将点击传播到较低的 UI 层!”) - 此 Log.d 语句指示我打算将点击事件传播到较低的 UI 层的时刻。我的问题是:我怎样才能实现这一目标?

目前,无论我点击圆内还是圆外的位置,我都无法将该点击传播到较低的 UI 层。

我期望的是,如果我在圆圈外单击,则该单击将被忽略。但是,如果我在圆圈内单击,则单击会传播到较低的 UI 层,从而导致打开关卡(因为该圆圈位于关卡打开按钮上方)。

注意:1)不使用“pointerInput(Unit)”也可以完成此操作,但我不确定如何做到这一点,所以我暂时选择使用“pointerInput(Unit)”。 2) 当我引用“上方”或“下方”UI 层时,我使用的是 .zIndex()。如果指数较大,则表示“上方”,如果指数较小,则表示“下方”。

kotlin android-jetpack-compose android-jetpack
1个回答
0
投票

使用 Modifier.clickable 或 PointerEventScope.detectTapGestures,您无法将手势传播到后代或父级,因为它们调用

PointeEventChange.consume()
,您可以在 Jetpack Compose 中查看有关手势和手势传播的答案。但在你的情况下,你似乎试图将事件传播到不可能的位置,在我看来,这是一个糟糕的设计。

对于任何想要将手势传递给父母而不是兄弟姐妹的人,您可以使用

awaitEachGesture
轻松编写自定义上下检测手势。

@Preview
@Composable
private fun GesturePrpoagation() {

    val context = LocalContext.current

    Box(modifier = Modifier
        .fillMaxSize()
        .border(2.dp, Color.Red)
        .pointerInput(Unit) {
//            detectTapGestures {
//                Toast.makeText(context, "Parent tapped", Toast.LENGTH_SHORT).show()
//            }
            awaitEachGesture {
                val down: PointerInputChange = awaitFirstDown()
                val up: PointerInputChange? = waitForUpOrCancellation()
                Toast.makeText(context, "Parent tapped", Toast.LENGTH_SHORT).show()
            }
        }
    ) {

        Box(modifier = Modifier
            .background(Color.Red)
            .fillMaxSize()
            .zIndex(1f)
            .pointerInput(Unit) {
//                detectTapGestures {
//                    Toast.makeText(context, "Box1 tapped", Toast.LENGTH_SHORT).show()
//                }
                awaitEachGesture {
                    val down: PointerInputChange = awaitFirstDown()
                    val up: PointerInputChange? = waitForUpOrCancellation()
                    Toast.makeText(context, "Box1 tapped", Toast.LENGTH_SHORT).show()
                }
            }
        )

        Box(modifier = Modifier
            .background(Color.Black.copy(alpha = .5f))
            .fillMaxSize()
            .zIndex(2f)
            .pointerInput(Unit) {
//                detectTapGestures {
//                    Toast.makeText(context, "Box2 tapped", Toast.LENGTH_SHORT).show()
//                }
                awaitEachGesture {
                    val down: PointerInputChange = awaitFirstDown()
                    val up: PointerInputChange? = waitForUpOrCancellation()
                    Toast.makeText(context, "Box2 tapped", Toast.LENGTH_SHORT).show()
                }
            }
        )
    }
}

根据 Box1 和 Box2 的 zIndex,您将看到 zIndex 较大的那个将触发事件,如果默认情况下均为零,则第二个 Box 将触发事件,然后传播到父级。您可以通过食用其中任何一个来选择性地改变这一点

PointerInputChange

在 OP 情况下,不需要第二个盒子。可以使用 Modifier.drawWithContent 绘制带有圆形剪辑的黑色透明层,并根据触摸位置触发该层或任何组件中的单击事件。

您可以看到,虽然点击手势被消耗,但除非您消耗手势,否则它们会被传播。

@Preview
@Composable
private fun TouchLayerSample() {

    var isTouched by remember {
        mutableStateOf(false)
    }

    val context = LocalContext.current

    Column(
        modifier = Modifier.fillMaxSize()
            .pointerInput(Unit) {
                val size = this.size
                val center = size.center
                detectTapGestures { offset: Offset ->

                    val circleCenter = Offset(
                        x = center.x.toFloat(),
                        y = size.height * .3f
                    )

                    // This is for measuring distance from center of circle to
                    // touch position to invoke only invoke when touch is inside circle
                    isTouched = isTouched(
                        center = circleCenter,
                        touchPosition = offset,
                        radius = 200f
                    )

                    if (isTouched) {
                        Toast.makeText(context, "Circle is touched", Toast.LENGTH_SHORT).show()
                    }

                }
            }
            .drawWithCache {

                val center = this.size.center

                val circlePath = Path().apply {
                    addOval(
                        Rect(
                            center = Offset(
                                x = center.x,
                                y = size.height * .3f
                            ),
                            radius = 200f
                        )
                    )
                }
                onDrawWithContent {
                    drawContent()
                    clipPath(circlePath, clipOp = ClipOp.Difference) {
                        drawRect(SolidColor(Color.Black.copy(alpha = .8f)))
                    }

                }
            }
    ) {

        // This you content that is behind transparent black layer.
        Image(
            modifier = Modifier.fillMaxSize(),
            painter = painterResource(R.drawable.landscape1),
            contentDescription = null,
            contentScale = ContentScale.FillBounds
        )
    }
}

private fun isTouched(center: Offset, touchPosition: Offset, radius: Float): Boolean {
    return center.minus(touchPosition).getDistanceSquared() < radius * radius
}

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