Android Compose 为什么当父级可点击时 Checkbox 需要 onCheckChanged?

Hoi*_*ear 2 android android-jetpack-compose android-jetpack-compose-gesture

我试图有一个带有一些尾随文本的复选框。这个想法是,无论您选择复选框本身还是文本,值都会被切换。为了代码的简洁性,我已经在可组合项中完成了所有操作。

选择文本时它会按预期工作。但是当我专门按下该复选框时,什么也没有发生。我看到按下复选框会产生连锁反应,但它显然忽略了该行的可点击功能并点击onCheckChange = {}

我对可组合作用域并不完全熟悉,所以我认为这可能是由于它Row()是一个内联函数,所以我尝试对 the 应用相同的修饰符,Surface但这也不起作用。

从两个地点打同样的电话没什么大不了的,我只是想知道为什么。

@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")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Thr*_*ian 5

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")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

Result

在此输入图像描述

Click propagation sample

您可以单击可组合项下方的按钮,即使Button其是在引擎盖下的SurfaceModifier.clickable

让我们为下面的父级创建我们自己的自定义触摸Button

private fun Modifier.customTouch(
    pass: PointerEventPass,
    onDown: () -> Unit,
    onUp: () -> Unit
) = this.then(
    Modifier.pointerInput(pass) {
        awaitEachGesture {
            awaitFirstDown(pass = pass)
            onDown()
            waitForUpOrCancellation(pass)
            onUp()
        }
    }
)
Run Code Online (Sandbox Code Playgroud)

并使用 PointerEventPass.Initial 将其分配给父级,无需调用消费,结果如下

在此输入图像描述

@Preview
@Composable
private fun GesturePropagationTest() {

    val context = LocalContext.current

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