在 Desktop Compose 中,如何检测按钮后面表面的点击 [英] In Desktop Compose how can I detect a click with the surface behind a button

查看:33
本文介绍了在 Desktop Compose 中,如何检测按钮后面表面的点击的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

@OptIn(ExperimentalComposeUiApi::class, androidx.compose.foundation.ExperimentalDesktopApi::class)@可组合有趣的例子(){卡片(修饰符 = 修饰符.width(150.dp).height(64.dp).mouseClickable { println(点击的卡片") }){柱子 {Button({ println("Clicked button")}) { Text("Click me") }}}}

谢谢.

解决方案

当按钮找到点击事件时,它会将其标记为已消耗,以防止其他视图接收它.这是通过 consumeDownChange() 完成的,您可以看到 detectTapAndPress 方法,其中使用 Button

The below code is for Jetbrains Desktop Compose. It shows a card with a button on it, right now if you click the card "clicked card" will be echoed to console. If you click the button it will echo "Clicked button"

However, I'm looking for a way for the card to detect the click on the button. I'd like to do this without changing the button so the button doesn't need to know about the card it's on. I wish to do this so the card knows something on it's surface is handled and for example show a differently colored border..

The desired result is that when you click on the button the log will echo both the "Card clicked" and "Button clicked" lines. I understand why mouseClickable isn't called, button declares the click handled. So I'm expecting that I'd need to use another mouse method than mouseClickable. But I can't for the life of me figure out what I should be using.

@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") }
        }
    }
}

Thank you.

解决方案

When button finds tap event, it marks it as consumed, which prevents other views from receiving it. This is done with consumeDownChange(), you can see detectTapAndPress method where this is done with Button here

To override the default behaviour, you had to reimplement some of gesture tracking. List of changes comparing to system detectTapAndPress:

  1. I use awaitFirstDown(requireUnconsumed = false) instead of default requireUnconsumed = true to make sure we get even a consumed even
  2. I use my own waitForUpOrCancellationInitial instead of waitForUpOrCancellation: here I use awaitPointerEvent(PointerEventPass.Initial) instead of awaitPointerEvent(PointerEventPass.Main), in order to get the event even if an other view will get it.
  3. Remove up.consumeDownChange() to allow the button to process the touch.

Final code:

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
        }
    }
}

P.S. you need to add implementation(compose("org.jetbrains.compose.ui:ui-util")) into your build.gradle.kts to use fastAll/fastAny.

Usage:

Card(
    modifier = Modifier
        .width(150.dp).height(64.dp)
        .clickable { }
        .pointerInput(Unit) {
            detectTapAndPressUnconsumed(onTap = {
                println("tap")
            })
        }
) {
    Column {
        Button({ println("Clicked button") }) { Text("Click me") }
    }
}

这篇关于在 Desktop Compose 中,如何检测按钮后面表面的点击的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆