喷气背包Compose:TextField点击无效

26

由于某些原因,Compose TextField 的点击监听器对我不起作用。

@Composable
    private fun ExposedDropdown(
        modifier: Modifier,
        list: List<String>,
        priority: Int
    ) {
        var expanded by remember { mutableStateOf(false) }
        Column(modifier) {
            OutlinedTextField(
                value = list[priority],
                onValueChange = { },
                readOnly = true,
                singleLine = true,
                label = { Text(stringResource(id = R.string.status)) },
                modifier = Modifier
                    .fillMaxWidth()
                    .clickable { Timber.i("Not working :(") }
                    .onFocusChanged { if (it.isFocused) expanded = !expanded },
                trailingIcon = {
                    Icon(
                        imageVector = Icons.Outlined.ArrowDropDown,
                        contentDescription = null,
                        modifier = Modifier
                            .clickable { expanded = !expanded }
                            .padding(16.dp)
                    )
                }
            )
            DropdownMenu(
                expanded = expanded,
                onDismissRequest = { expanded = false }
            ) {
                list.forEach { label ->
                    DropdownMenuItem(onClick = {
                        viewModel.setPriority(list.indexOf(label))
                        expanded = false
                    }) {
                        Text(text = label)
                    }
                }
            }
        }
    }

从我的解决方案中可以看出,我使用了onFocusChanged,但它并不起作用。

需要背景的人们请注意,我正在尝试做ExposedDropdown,但我希望在单击TextField的任何地方时都能打开它。

7个回答

33

另一个可能的解决方法是这样的:

import kotlinx.coroutines.flow.collect

TextField(
    value = ...,
    onValueChange = { ... },
    interactionSource = remember { MutableInteractionSource() }
        .also { interactionSource ->
            LaunchedEffect(interactionSource) {
                interactionSource.interactions.collect {
                    if (it is PressInteraction.Release) {
                        // works like onClick
                    }
                }
            }
        }
)


6
为了避免其他人遇到问题,请确保使用 import kotlinx.coroutines.flow.collect 作为 collect 的源,否则您将会得到一个奇怪的异常。显然 Kotlin 中的某个人决定在同一个库中有第二个名为 collect 的方法 XD。 - quealegriamasalegre
终于有人给出了正确的答案,使用 if (it is PressInteraction.Release) { 进行检查。有很多答案只是使用 val isPressed: Boolean by interactionSource.collectIsPressedAsState(),这会在您轻触屏幕时立即触发,而不需要释放手指。 - user924
1
值得注意的是: 不强制设置 enabled = false 来接收回调。 - Irwin Nawrocki
2
如何添加涟漪效果? - Risal Fajar Amiyardi

22

使用compose 1.0.2 ,默认情况下它可以工作。需要添加一行enabled = false

示例


@Composable
fun SelectableTextField(
    modifier: Modifier = Modifier,
    textValue: String,
    onClick: () -> Unit
) {
    TextField(
        value = textValue,
        onValueChange = {},
        modifier = modifier
            .fillMaxWidth()
            .clickable { onClick() },
        enabled = false
    )
}

要消除水波纹效果,请使用此扩展。

inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier =
    composed {
        clickable(indication = null,
            interactionSource = remember { MutableInteractionSource() }) {
            onClick()
        }
    }

2
为什么我们必须要有 enabled = false 才能让 clickable 生效? - dazza5000
2
发现了这个问题,原因在于 https://issuetracker.google.com/issues/172154008 - 如果您认为应该有不同的解决方案,请给它点赞或发表评论。 - dazza5000

10
设置enabled属性为false的建议是有效的,但这会影响样式。 如果你希望它在视觉上与一个启用的文本字段匹配,那么请相应地设置colors属性。
OutlinedTextField(
   value = "Example",
   enabled = false,
   onValueChange = {},
   modifier = Modifier.clickable { doSomeBehavior() },
   colors = TextFieldDefaults.outlinedTextFieldColors(
      disabledTextColor = MaterialTheme.colorScheme.onSurface,
      disabledBorderColor = MaterialTheme.colorScheme.outline,
      disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant,
      disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
      //For Icons
      disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
      disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant)
)

9
val interactionSource = remember { MutableInteractionSource() }
val isPressed: Boolean by interactionSource.collectIsPressedAsState()

LaunchedEffect(isPressed){
    if (isPressed) {
        // Click action
    }
}

TextField(
value = textFieldValue,
onValueChange = onTextFieldChange,
interactionSource = interactionSource
)

如何测试这个功能?虽然performClick存在,但无法触发点击回调 :/ - Song
1
这可能会运行isPressed处理程序任意次数。它应该使用LaunchedEffect,例如LaunchedEffect(isPressed) { if(isPressed) { /* Click Action */ }} - Mike Dawson
1
这并不正确,它会在你点击字段时立即触发,甚至没有移开手指,应该在释放手指后才调用它,当你有许多文本字段时,甚至无法正常滚动列。 - user924

8

clickable修饰符目前(1.0.0-beta08)不能与TextField一起使用。

这只是一个解决方法,不是真正的解决方案。
由于你的TextField是只读的,所以你可以将OutlinedTextField包裹在一个Box中,再使用第二个Box来处理点击事件。

 val focusRequester = FocusRequester.createRefs()
 val interactionSource = remember { MutableInteractionSource() }

 Box() {
        OutlinedTextField(
          //...your code
          modifier = Modifier
            .fillMaxWidth()
            .focusRequester(focusRequester)
        }
                    
        if (!expanded) {
            Box(modifier = Modifier
                .matchParentSize()
                .clickable(
                    onClick = {
                        expanded = !expanded
                        focusRequester.requestFocus() //to give the focus to the TextField
                              },
                    interactionSource = interactionSource,
                    indication = null //to avoid the ripple on the Box
                ))
        }
    }

谢谢,但是使用这种解决方案我将无法复制或与TextField的内容进行交互,对吗? - androboy
@androboy 不是的,在你的代码中是 enable 但是 readOnly = true。你也想支持选择/复制吗? - Gabriele Mariotti
不需要真的必要,但翻译后的文本会更好。 - androboy

2

这里有一个可能的解决方案。我基于androidx.compose.foundation.interaction.collectIsPressedAsState创建了一个collectClickAsState()组合函数:

@Composable
fun CustomTextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    onClick: (() -> Unit)? = null,
) {
    val onClickSource = remember { MutableInteractionSource() }

    if (onClick != null) {
        if (onClickSource.collectClickAsState().value) {
            onClick.invoke()
        }
    }

    TextField(
        value = value,
        onValueChange = onValueChange,
        interactionSource = onClickSource,
        enabled = enabled,
        readOnly = onClick != null,
        modifier = modifier
            // add clickable to work with talkback/accessibility
            .clickable(enabled = enabled) { onClick?.invoke() },
    )
}

@Composable
fun InteractionSource.collectClickAsState(): State<Boolean> {
    val onClick = remember { mutableStateOf(false) }
    LaunchedEffect(this) {
        var wasPressed = false
        interactions.collect { interaction ->
            when (interaction) {
                is PressInteraction.Press -> {
                    wasPressed = true
                }
                is PressInteraction.Release -> {
                    if (wasPressed) onClick.value = true
                    wasPressed = false
                }
                is PressInteraction.Cancel -> {
                    wasPressed = false
                }
            }
            // reset state with some delay otherwise it can re-emit the previous state
            delay(10L)
            onClick.value = false
        }
    }
    return onClick
}

使用这个解决方案,文本字段仍然可以聚焦,文本可以选择,并且将使用正确的UI启用状态。

1
TextField(
        modifier = Modifier
            .fillMaxWidth()
            .**clickable**(onClick = onClick),
        enabled = false,
        value = currentSelection,
        onValueChange = { },
        label = {
            
        }
    )

使用 enabled = false 和 clickable 修饰符。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接