我正在努力为我的应用程序创建“入门”体验,这本质上是关于如何使用该应用程序的快速指南。为了实现这一目标,我希望用户单击应用程序指示他们的位置。具体来说,我想创建一个用户界面,其中包含允许用户单击的圆形元素和不允许单击的灰色透明背景(为了清楚起见,请参阅附图)
我当前的挑战在于弄清楚如何将此单击事件传播到较低的 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()。如果指数较大,则表示“上方”,如果指数较小,则表示“下方”。
使用 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
}