1

以下代码适用于 Jetbrains Desktop Compose。它显示一张带有按钮的卡片,现在如果您单击卡片,“点击的卡片”将回显到控制台。如果您单击按钮,它将回显“单击按钮”

但是,我正在寻找一种让卡片检测按钮点击的方法。我想在不更改按钮的情况下执行此操作,因此按钮不需要知道它所在的卡。我希望这样做,以便卡片知道其表面上的某些东西已被处理,例如显示不同颜色的边框。

期望的结果是,当您单击按钮时,日志将回显“Card clicked”和“Button clicked”行。我明白为什么mouseClickable不调用,按钮声明点击已处理。所以我期望我需要使用另一种鼠标方法而不是mouseClickable. 但我无法终生弄清楚我应该使用什么。

@OptIn(ExperimentalComposeUiApi::class, androidx.compose.foundation.ExperimentalDesktopApi::class)
@Composable
fun example() {
    Card(
        modifier = Modifier
            .width(150.dp).height(64.dp)
            .mouseClickable { println("Clicked card") }
    ) {
        Column {
            Button({ println("Clicked button")}) { Text("Click me") }
        }
    }
}
4

1 回答 1

5

当按钮发现点击事件时,它会将其标记为已使用,这会阻止其他视图接收它。这是通过 完成的consumeDownChange(),您可以在此处detectTapAndPress查看完成此操作的方法Button

要覆盖默认行为,您必须重新实现一些手势跟踪。与系统相比的更改列表detectTapAndPress

  1. 我使用awaitFirstDown(requireUnconsumed = false)而不是默认值requireUnconsumed = true来确保我们得到甚至消耗
  2. 我使用我自己的waitForUpOrCancellationInitial代替waitForUpOrCancellation: 在这里我使用awaitPointerEvent(PointerEventPass.Initial)代替awaitPointerEvent(PointerEventPass.Main), 以获取事件,即使其他视图会获取它。
  3. 移除up.consumeDownChange()以允许按钮处理触摸。

最终代码:

suspend fun PointerInputScope.detectTapAndPressUnconsumed(
    onPress: suspend PressGestureScope.(Offset) -> Unit = NoPressGesture,
    onTap: ((Offset) -> Unit)? = null
) {
    val pressScope = PressGestureScopeImpl(this)
    forEachGesture {
        coroutineScope {
            pressScope.reset()
            awaitPointerEventScope {

                val down = awaitFirstDown(requireUnconsumed = false).also { it.consumeDownChange() }

                if (onPress !== NoPressGesture) {
                    launch { pressScope.onPress(down.position) }
                }

                val up = waitForUpOrCancellationInitial()
                if (up == null) {
                    pressScope.cancel() // tap-up was canceled
                } else {
                    pressScope.release()
                    onTap?.invoke(up.position)
                }
            }
        }
    }
}

suspend fun AwaitPointerEventScope.waitForUpOrCancellationInitial(): PointerInputChange? {
    while (true) {
        val event = awaitPointerEvent(PointerEventPass.Initial)
        if (event.changes.fastAll { it.changedToUp() }) {
            // All pointers are up
            return event.changes[0]
        }

        if (event.changes.fastAny { it.consumed.downChange || it.isOutOfBounds(size) }) {
            return null // Canceled
        }

        // Check for cancel by position consumption. We can look on the Final pass of the
        // existing pointer event because it comes after the Main pass we checked above.
        val consumeCheck = awaitPointerEvent(PointerEventPass.Final)
        if (consumeCheck.changes.fastAny { it.positionChangeConsumed() }) {
            return null
        }
    }
}

PS 您需要将implementation("androidx.compose.ui:ui-util:$compose_version")Android Compose 或implementation(compose("org.jetbrains.compose.ui:ui-util"))Desktop Compose 添加到您build.gradle.kts的使用fastAll/fastAny中。

用法:

Card(
    modifier = Modifier
        .width(150.dp).height(64.dp)
        .clickable { }
        .pointerInput(Unit) {
            detectTapAndPressUnconsumed(onTap = {
                println("tap")
            })
        }
) {
    Column {
        Button({ println("Clicked button") }) { Text("Click me") }
    }
}
于 2021-08-22T06:02:41.543 回答