Android Jetpack Compose:修改输入文本后键盘从数字更改为字母

问题描述 投票:0回答:3

在 Jetpack Compose 中探索

TextField
时,我遇到了一种情况,我必须修改在字段中键入的输入。 例如输入3个字符后添加逗号

我就是这样做的。

@Composable
fun TFDemo() {
    var fieldValue by remember { mutableStateOf(TextFieldValue("")) }

    TextField(
        value = fieldValue,
        onValueChange = {
            val newMessage = it.text.let { text -> if (text.length == 3) "$text," else text }
            fieldValue = it.copy(newMessage, selection = TextRange(newMessage.length))
        },
        keyboardOptions = KeyboardOptions(autoCorrect = false),
    )
}

但是运行后,我意识到添加逗号后,键盘视图从数字/符号变回字母,这不应该是这种情况。 请参阅下面的视频输出以获得清晰度

正如您在下面的视频中看到的,当我输入“111”时,附加了逗号,突然键盘的数字视图再次变为字母。


这里我修改了

selection
TextFieldValue
,以便每当附加逗号时光标始终位于消息的末尾。

android android-layout user-interface textfield android-jetpack-compose
3个回答
6
投票

这种情况正是

VisualTransformation
的用途。

这是 Google 员工对另一个问题的评论:

我认为我们无法轻易解决这个问题。

一般不建议在 onValueChanged 回调中过滤文本,因为文本状态与进程 IME(软件键盘)共享。过滤文本是指文本内容在内部发生变化,然后将新的状态通知给IME。这不是 IME 的正常路径,不同的 IME 对这种意外状态变化的反应也不同。一些IME可能会尝试重建组合,另一些IME可能会放弃并开始新的会话等。这主要是由于历史原因,从现在起很难修复。因此,请避免在 onValueChanged 回调中过滤文本并考虑以下替代方案:

  1. (推荐)不要过滤它并显示错误消息。 (此处无关)
  2. 使用 VisualTransformation 更改视觉输出,而无需修改编辑缓冲区。

2
投票

根据上面提到的答案,

VisualTransformation
是这种情况的完美解决方案,我们不应该直接修改TextField的缓冲区。因为
VisualTransformation
只是改变文本的视觉输出,而不是实际文本。

我在这里写了一篇关于此场景的文章,我对此进行了详细解释。

解决方案:

@Composable
fun TextFieldDemo() {
    var message by remember { mutableStateOf("") }

    TextField(
        value = message,
        placeholder = { Text("Enter amount or message") },
        onValueChange = { message = it },
        visualTransformation = AmountOrMessageVisualTransformation()
    )
}

class AmountOrMessageVisualTransformation : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {

        val originalText = text.text
        val formattedText = formatAmountOrMessage(text.text)

        val offsetMapping = object : OffsetMapping {

            override fun originalToTransformed(offset: Int): Int {
                if (originalText.isValidFormattableAmount) {
                    val commas = formattedText.count { it == ',' }
                    return when {
                        offset <= 1 -> offset
                        offset <= 3 -> if (commas >= 1) offset + 1 else offset
                        offset <= 5 -> if (commas == 2) offset + 2 else offset + 1
                        else -> 8
                    }
                }
                return offset
            }

            override fun transformedToOriginal(offset: Int): Int {
                if (originalText.isValidFormattableAmount) {
                    val commas = formattedText.count { it == ',' }
                    return when (offset) {
                        8, 7 -> offset - 2
                        6 -> if (commas == 1) 5 else 4
                        5 -> if (commas == 1) 4 else if (commas == 2) 3 else offset
                        4, 3 -> if (commas >= 1) offset - 1 else offset
                        2 -> if (commas == 2) 1 else offset
                        else -> offset
                    }
                }
                return offset
            }
        }

        return TransformedText(
            text = AnnotatedString(formattedText),
            offsetMapping = offsetMapping
        )
    }
}

0
投票
如果更改

TextFieldValue 类中的 selection 字段,键盘仍会从数字更改“语言”。如果您无论如何需要将 TextFieldValue 放入(例如) BasicTextField 可组合函数的 value 字段,并将其更改为 onValueChange lambda,那么您确实有一次机会更改可见值并将光标移动到末尾 -使用视觉变换。 但问题是,您需要为每种情况编写单独的转换,并在将转换传递给 fun 参数之前检查应应用哪个转换。

就我而言,如果用户开始写没有 7 的号码,我需要在电话号码前加上 7,如果他这样做了,则不需要。因此,当我需要 TextField 值时,我需要再次检查它,因为它对我来说是不同的值,但对用户来说不是。

在您的情况下,用户可见的 coma 实际上不在您的 TextField 字符串值中(如果您使用 VisualTransformation),因此如果您需要将其传递到任何地方,则需要在传递之前手动将其放入字符串中。

                BasicTextField(
                    keyboardOptions = ...,
                    value = textState.value,
                    onValueChange = { expectedNewValue ->
                        val newValueText = expectedNewValue.text
                        ...
                        textState.value =
                            textState.value.copy(
                                selection = TextRange(
                                    newValueText.length // DONT CHANGE THIS VALUE
                                ),
                                text = textFieldValue.value?.value ?: "",
                                composition = if (textFieldValue.value is AuthTextInput.Email) TextRange(0, newValueText.length)
                                    else TextRange(newValueText.length)
                            )
                        ...
                    },
                    visualTransformation =
                    if (textFieldValue.value is AuthTextInput.PhoneNumber) {
                        val mask = if (textFieldValue.value?.value?.startsWith('7') == true) "+X(XXX)-XXX-XX-XX" else "+7(XXX)-XXX-XX-XX"
                        PhoneVisualTransformation(mask, 'X')
                    }
                    else VisualTransformation.None,
                    ...
                )

PhoneVisualTransformation 是带有 mask 的 VisualTransformation 的自定义类,您可以按照您的需要实现它或在 SO 中找到实现。 也许这个例子对任何人都有用:)

© www.soinside.com 2019 - 2024. All rights reserved.