Android Compose为什么在父项可点击时,复选框需要onCheckChanged?

huangapple go评论65阅读模式
英文:

Android Compose why does Checkbox need onCheckChanged when parent is clickable?

问题

I am trying to have a checkbox with some trailing text. The idea is that whether you select the checkbox itself, or the text, the value is toggled. For brevity in code I've done everything in the composable.

It works as expected when selecting the text. But when I specifically press the checkbox, nothing happens. I see the ripple effect from pressing the checkbox but it is obviously ignoring the row's clickable function and hitting onCheckChange = {}

I'm not entirely fluent in composable scopes so I thought it might be due to Row() being an inline function so I tried applying the same modifier to the Surface but that doesn't work either.

It isn't a big deal having the same call from the 2 spots, I would just like to know why.

英文:

I am trying to have a checkbox with some trailing text. The idea is that whether you select the checkbox itself, or the text, the value is toggled. For brevity in code I've done everything in the composable.

It works as expected when selecting the text. But when I specifically press the checkbox, nothing happens. I see the ripple effect from pressing the checkbox but it is obviously ignoring the row's clickable function and hitting onCheckChange = {}

I'm not entirely fluent in composable scopes so I thought it might be due to Row() being an inline function so I tried applying the same modifier to the Surface but that doesn't work either.

It isn't a big deal having the same call from the 2 spots, I would just like to know why.

@Composable
fun CheckboxInRowWithText() {
    var checkedState by remember { mutableStateOf(false) }
    Surface {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    checkedState = !checkedState
                },
            verticalAlignment = Alignment.CenterVertically) {
            Checkbox(checked = checkedState, onCheckedChange = {} )

            Text(text = "Clicking this Row will toggle checkbox")
        }
    }
}

答案1

得分: 3

以下是您提供的文本的中文翻译部分:

无法单击祖先 Composable 的原因不是因为它位于另一个 Composable 下面,而是因为默认情况下触摸传播从后代到祖先,这是由于 PointerEventPass(Main)导致的。

Modifier.clickable 不允许父元素获取事件的原因是它调用了 PointerEventChange.consume,但您可以编写自己的点击函数,以从子元素传播到父元素或从父元素传播到子元素或有条件地消耗事件。

您可以查看 此答案 以获取更多详细信息。

您在 checkBoxChange 上基本上什么都没做,这就是它不会改变的原因,请在 onCheckedChange 中设置 checkedState。

@Composable
fun CheckboxInRowWithText() {
    val context = LocalContext.current

    var checkedState by remember { mutableStateOf(false) }
    Surface {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    Toast
                        .makeText(context, "Row clicked", Toast.LENGTH_SHORT)
                        .show()
                    checkedState = !checkedState
                },
            verticalAlignment = Alignment.CenterVertically
        ) {
            Checkbox(checked = checkedState, onCheckedChange = {
                Toast.makeText(context, "Checkbox check", Toast.LENGTH_SHORT).show()
                checkedState = it
            })
            Text(text = "Clicking this Row will toggle checkbox")
        }
    }
}

您还可以像这样构建它,即使单击 CheckBox 时也会显示涟漪效果。

@Composable
fun CheckBoxWithTextRippleFullRow(
    label: String,
    state: Boolean,
    onStateChange: (Boolean) -> Unit
) {
    // 带有文本的复选框在右侧
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .height(40.dp)
            .clickable(
                role = Role.Checkbox,
                onClick = {
                    onStateChange(!state)
                }
            )
            .padding(8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Checkbox(
            checked = state,
            onCheckedChange = null
        )
        Spacer(modifier = Modifier.width(8.dp))
        Text(text = label)
    }
}

希望这些翻译对您有所帮助。

英文:

The reason you can't click an ancestor Composable is not because it's under another, touch propagation goes from descendant to ancestor by default because of PointerEventPass which is Main.

The reason Modifier.clickable doesn't allow parent to get event is, it calls PointerEventChange.consume but you can write your own click functions to propagate from child to parent or parent to child or consume conditionally.

You can check out this answer for more details

You are basically doing nothing on checkBoxChange that's why it doesn't change, set checkedState inside onCheckedChange.

@Composable
fun CheckboxInRowWithText() {

    val context = LocalContext.current

    var checkedState by remember { mutableStateOf(false) }
    Surface {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    Toast
                        .makeText(context, "Row clicked", Toast.LENGTH_SHORT)
                        .show()
                    checkedState = !checkedState
                },
            verticalAlignment = Alignment.CenterVertically) {
            Checkbox(checked = checkedState, onCheckedChange = {
                Toast.makeText(context, "Checkbox check", Toast.LENGTH_SHORT).show()
                checkedState = it
            }
            )

            Text(text = "Clicking this Row will toggle checkbox")
        }
    }
}

Also you can build it like this which will show ripple even when you click CheckBox.

@Composable
 fun CheckBoxWithTextRippleFullRow(
    label: String,
    state: Boolean,
    onStateChange: (Boolean) -> Unit
) {

    // Checkbox with text on right side
    Row(modifier = Modifier
        .fillMaxWidth()
        .height(40.dp)
        .clickable(
            role = Role.Checkbox,
            onClick = {
                onStateChange(!state)
            }
        )
        .padding(8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Checkbox(
            checked = state,
            onCheckedChange = null
        )
        Spacer(modifier = Modifier.width(8.dp))
        Text(text = label)
    }
}

Result

Android Compose为什么在父项可点击时,复选框需要onCheckChanged?

<h2>Click propagation sample</h2>

You can click below a Composable even with a Button which is a Surface with Modifier.clickable under the hood.

Let's create our own custom touch only for the parent below Button

private fun Modifier.customTouch(
    pass: PointerEventPass,
    onDown: () -&gt; Unit,
    onUp: () -&gt; Unit
) = this.then(
    Modifier.pointerInput(pass) {
        awaitEachGesture {
            awaitFirstDown(pass = pass)
            onDown()
            waitForUpOrCancellation(pass)
            onUp()
        }
    }
)

And assign it to parent with PointerEventPass.Initial with no consume call and will result as

Android Compose为什么在父项可点击时,复选框需要onCheckChanged?

@Preview
@Composable
private fun LookAheadLayoutTest() {

    val context = LocalContext.current

    Box(
        modifier = Modifier
            .customTouch(
                pass = PointerEventPass.Initial,
                onDown = {
                    Toast
                        .makeText(context, &quot;Parent down&quot;, Toast.LENGTH_SHORT)
                        .show()
                }, onUp = {
                    Toast
                        .makeText(context, &quot;Parent up&quot;, Toast.LENGTH_SHORT)
                        .show()
                }
            )
            .padding(bottom = 50.dp)
            .fillMaxSize()
            .padding(20.dp),
        contentAlignment = Alignment.BottomCenter
    ) {
        Button(
            onClick = {
            Toast.makeText(context, &quot;Button is clicked&quot;, Toast.LENGTH_SHORT).show()
        }) {
            Text(&quot;Click Button&quot;)
        }
    }
}

答案2

得分: 0

Children are simply drawn on top of their parent container, that's the only way it makes sense. If you set background to that Checkbox, you'd expect it to cover the background of the Row, no? It's the same with clicking. You can't click on something that's below other clickable element. It's the same for Compose and View system.

英文:

Children are simply drawn on top of their parent container, that's the only way it makes sense. If you set background to that Checkbox, you'd expect it to cover background of the Row, no? It's same with clicking. You can't click on something that's below other clickable element. It's the same for Compose and View system.

huangapple
  • 本文由 发表于 2023年5月11日 19:00:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76226889.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定