【问题标题】:How can I detect a click with the view behind a Jetpack Compose Button?如何使用 Jetpack Compose 按钮​​后面的视图检测点击?
【发布时间】:2021-08-22 00:39:05
【问题描述】:

以下代码适用于 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") }
        }
    }
}

【问题讨论】:

    标签: kotlin android-jetpack-compose compose-desktop


    【解决方案1】:

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

    要覆盖默认行为,您必须重新实现一些手势跟踪。对比系统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
            }
        }
    }
    

    附:您需要将 Android Compose 的 implementation("androidx.compose.ui:ui-util:$compose_version") 或 Desktop Compose 的 implementation(compose("org.jetbrains.compose.ui:ui-util")) 添加到您的 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") }
        }
    }
    

    【讨论】:

    • 我希望有一种方法可以将项目放在背景和前景不需要相互了解的背景上。而实现自己的按钮并不完全如此。特别是因为您还需要实现自己的 TextFields 和所有其他侦听鼠标事件的控件。但是,我想这和 compose 一样好。我想我将不得不寻找另一种方式。谢谢你..
    • @PhilipDukhov 如何更改此代码以防止快速点击?
    • @willow512 其实我错了,你不需要重新实现所有元素的触摸处理。查看更新的答案
    • @Dr.jacky 请查看更新的答案,因为它不正确。 “快速点击”是什么意思?也许您需要长按检测而不是普通的检测?
    • 我的意思和我们在 RxJava 中的throttleFirst 一样
    猜你喜欢
    • 2022-10-05
    • 2021-11-13
    • 2022-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-10
    • 1970-01-01
    • 2013-09-01
    相关资源
    最近更新 更多