在 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
,以便每当附加逗号时光标始终位于消息的末尾。
这种情况正是
VisualTransformation
的用途。
这是 Google 员工对另一个问题的评论:
我认为我们无法轻易解决这个问题。
一般不建议在 onValueChanged 回调中过滤文本,因为文本状态与进程 IME(软件键盘)共享。过滤文本是指文本内容在内部发生变化,然后将新的状态通知给IME。这不是 IME 的正常路径,不同的 IME 对这种意外状态变化的反应也不同。一些IME可能会尝试重建组合,另一些IME可能会放弃并开始新的会话等。这主要是由于历史原因,从现在起很难修复。因此,请避免在 onValueChanged 回调中过滤文本并考虑以下替代方案:
- (推荐)不要过滤它并显示错误消息。 (此处无关)
- 使用 VisualTransformation 更改视觉输出,而无需修改编辑缓冲区。
根据上面提到的答案,
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
)
}
}
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 中找到实现。 也许这个例子对任何人都有用:)