kotlin Jetpack合成-减少点击以降低UI层

fykwrbwg  于 2023-10-23  发布在  Kotlin
关注(0)|答案(1)|浏览(127)

我正在为我的应用程序创建一个“入门”体验,这本质上是一个关于如何使用应用程序的快速指南。为了实现这一点,我希望用户点击应用程序指示他们的位置。具体来说,我想创建一个用户界面,其中允许用户点击的圆形元素,以及不允许点击的灰色透明背景(请参阅所附图片以了解详细信息)

我目前的挑战在于弄清楚如何将这个单击事件传播到较低的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-debugger 9”,“将单击扩展到较低的UI层!“)-此Log.d语句指示我打算将单击事件传播到较低UI层的时刻。我的问题是:我怎样才能做到这一点?
目前,无论我在哪里单击,在圆圈内部或外部,我都不能将单击传播到较低的UI层。
我所期望的是,如果我在圆圈外单击,则忽略单击。但是,如果我在圆圈内单击,则单击将传播到较低的UI层,这将导致打开一个级别(因为此圆圈位于级别打开按钮的上方)。
注:1)这可能可以在不使用'pointerInput(Unit)'的情况下完成,但我不确定如何做到这一点,所以我现在选择使用'pointerInput(Unit)'。2)当我引用“上面”或“下面”的UI层时,我使用的是.zIndex()。如果指数较大,则表示“高于”,如果指数较小,则表示“低于”。

yqkkidmi

yqkkidmi1#

使用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 can be any content that is behind transparent black layer.

        // If you have a clickable component here you can use isTouched with custom gesture in previous example to not consume it inside detectTapGesture, so it can be propagated to component. In that case you need to also change PointerEventPass to Initial to propagate from ancestor to descendant.

        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
}

相关问题