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

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

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

我目前的挑战在于弄清楚如何将这个单击事件传播到较低的UI层。
下面是相关的代码片段:

  1. Canvas(
  2. modifier = Modifier
  3. .fillMaxSize()
  4. .zIndex(3f)
  5. .pointerInput(Unit) {
  6. detectTapGestures(onTap = { offset ->
  7. if (!Rect(position, volume).contains(offset)) {
  8. Log.d("app-debugger9","Cancel the click!")
  9. }
  10. else
  11. {
  12. Log.d("app-debugger9","Propagate click to below UI layers!")
  13. }
  14. })
  15. }
  16. ) {
  17. val circlePath = Path().apply {
  18. addOval(Rect(position, volume))
  19. }
  20. clipPath(circlePath, clipOp = ClipOp.Difference) {
  21. drawRect(SolidColor(Color.Black.copy(alpha = alphaValue)))
  22. }
  23. }

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轻松编写自定义的上下检测手势。

  1. @Preview
  2. @Composable
  3. private fun GesturePrpoagation() {
  4. val context = LocalContext.current
  5. Box(modifier = Modifier
  6. .fillMaxSize()
  7. .border(2.dp, Color.Red)
  8. .pointerInput(Unit) {
  9. // detectTapGestures {
  10. // Toast.makeText(context, "Parent tapped", Toast.LENGTH_SHORT).show()
  11. // }
  12. awaitEachGesture {
  13. val down: PointerInputChange = awaitFirstDown()
  14. val up: PointerInputChange? = waitForUpOrCancellation()
  15. Toast.makeText(context, "Parent tapped", Toast.LENGTH_SHORT).show()
  16. }
  17. }
  18. ) {
  19. Box(modifier = Modifier
  20. .background(Color.Red)
  21. .fillMaxSize()
  22. .zIndex(1f)
  23. .pointerInput(Unit) {
  24. // detectTapGestures {
  25. // Toast.makeText(context, "Box1 tapped", Toast.LENGTH_SHORT).show()
  26. // }
  27. awaitEachGesture {
  28. val down: PointerInputChange = awaitFirstDown()
  29. val up: PointerInputChange? = waitForUpOrCancellation()
  30. Toast.makeText(context, "Box1 tapped", Toast.LENGTH_SHORT).show()
  31. }
  32. }
  33. )
  34. Box(modifier = Modifier
  35. .background(Color.Black.copy(alpha = .5f))
  36. .fillMaxSize()
  37. .zIndex(2f)
  38. .pointerInput(Unit) {
  39. // detectTapGestures {
  40. // Toast.makeText(context, "Box2 tapped", Toast.LENGTH_SHORT).show()
  41. // }
  42. awaitEachGesture {
  43. val down: PointerInputChange = awaitFirstDown()
  44. val up: PointerInputChange? = waitForUpOrCancellation()
  45. Toast.makeText(context, "Box2 tapped", Toast.LENGTH_SHORT).show()
  46. }
  47. }
  48. )
  49. }
  50. }

根据Box1和Box2的zIndex,您将看到该事件将为zIndex较大的一个触发,如果两个都为零(默认为零),则将为第二个Box触发,然后传播到父级。您可以通过使用这些PointerInputChange中的任何一个来选择性地更改它。
在OP情况下,不需要第二个盒子。带有圆形剪辑的黑色透明层可以使用Modifier.drawWithContent绘制,并基于触摸位置触发该层或任何组件中的单击事件。
你可以看到,当点击手势被消耗时,除非你消耗手势,否则手势会被传播。

  1. @Preview
  2. @Composable
  3. private fun TouchLayerSample() {
  4. var isTouched by remember {
  5. mutableStateOf(false)
  6. }
  7. val context = LocalContext.current
  8. Column(
  9. modifier = Modifier.fillMaxSize()
  10. .pointerInput(Unit) {
  11. val size = this.size
  12. val center = size.center
  13. detectTapGestures { offset: Offset ->
  14. val circleCenter = Offset(
  15. x = center.x.toFloat(),
  16. y = size.height * .3f
  17. )
  18. // This is for measuring distance from center of circle to
  19. // touch position to invoke only invoke when touch is inside circle
  20. isTouched = isTouched(
  21. center = circleCenter,
  22. touchPosition = offset,
  23. radius = 200f
  24. )
  25. if (isTouched) {
  26. Toast.makeText(context, "Circle is touched", Toast.LENGTH_SHORT).show()
  27. }
  28. }
  29. }
  30. .drawWithCache {
  31. val center = this.size.center
  32. val circlePath = Path().apply {
  33. addOval(
  34. Rect(
  35. center = Offset(
  36. x = center.x,
  37. y = size.height * .3f
  38. ),
  39. radius = 200f
  40. )
  41. )
  42. }
  43. onDrawWithContent {
  44. drawContent()
  45. clipPath(circlePath, clipOp = ClipOp.Difference) {
  46. drawRect(SolidColor(Color.Black.copy(alpha = .8f)))
  47. }
  48. }
  49. }
  50. ) {
  51. // This can be any content that is behind transparent black layer.
  52. // 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.
  53. Image(
  54. modifier = Modifier.fillMaxSize(),
  55. painter = painterResource(R.drawable.landscape1),
  56. contentDescription = null,
  57. contentScale = ContentScale.FillBounds
  58. )
  59. }
  60. }
  61. private fun isTouched(center: Offset, touchPosition: Offset, radius: Float): Boolean {
  62. return center.minus(touchPosition).getDistanceSquared() < radius * radius
  63. }

展开查看全部

相关问题