我想对 Jetpack Compose 中的 BasicTextField 应用视觉转换。我尝试了一些代码,但遇到了几个问题。例如,光标没有停留在正确的位置,按退格键时,光标移动到错误的位置。
我需要 BasicTextField 始终显示无法删除的特定字符串(例如“ABC-”),即使使用退格键或清除操作也是如此。此外,我想限制输入并应用屏蔽来格式化文本,如下所示:“ABC-1234-2345-6366”。
有人可以帮助我正确地实现这个吗?
@Preview(showBackground = true)
@Composable
private fun SampleTextFieldView() {
var textFieldValue by remember { mutableStateOf(TextFieldValue("ABC-")) }
BasicFieldView(
textFieldValue,
onValueChange = { onValueChange ->
val digitOnly = onValueChange.text.filter { it.isDigit() }
val formattedString = buildString {
append("Abc-")
digitOnly.forEachIndexed { index, digit ->
if (index % 4 == 0 && index !=0 ){
append("-")
}
append(digit)
}
}
textFieldValue = onValueChange.copy(formattedString)
}
)
}
@Composable
fun BasicFieldView(
textFieldValue: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize()
) {
BasicTextField(
modifier = Modifier.fillMaxWidth(),
value = textFieldValue,
onValueChange = {
onValueChange(it)
},
)
}
}
首先,您应该使用新的 BasicTextField 重载,它使用
TextFieldState
而不是 TextFieldValue
。这解决了有关监视文本字段值更新的许多问题,并接受 InputTransformation
来修改用户输入的数据,并接受 OutputTransformation
来修改文本字段值的显示方式。
现在,为了满足您的要求,需要以下内容:
要将输入限制为12个字符并确保只能输入数字,您需要InputTransformations。对于最大长度,您可以使用内置
InputTransformation.maxLength(12)
。要将输入限制为仅数字,您可以创建自定义输入转换:
object DigitsOnlyTransformation : InputTransformation {
override val keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
override fun TextFieldBuffer.transformInput() {
if (!asCharSequence().isDigitsOnly()) {
revertAllChanges()
}
}
}
keyboardOptions
也可以直接在BasicTextField中指定,但我发现将有关仅数字要求的所有限制捆绑在一处会更清晰。 TextFieldBuffer
可以根据需要修改。 revertAllChanges()
忽略最新更改。这与过滤掉所有非数字的实现有点不同。仅当用户一次输入多个字符时(例如从剪贴板粘贴混合数字和非数字),这才有意义。您可以根据需要进行调整。
这两个 InputTransformation 可以链接在一起,然后传递到 BasicTextField,如下所示:
BasicTextField(
...
inputTransformation = InputTransformation.maxLength(12)
.then(DigitsOnlyTransformation),
)
要为显示提供带有破折号的蒙版,只需创建一个新的OutputTransformation。 OutputTransformations 的工作方式与 InputTransformations 相同,只是它们不会更改文本字段的值,输出仅用于 UI 中的显示:
@Stable
data class GroupingOutputTransformation(
private val groupSize: Int,
private val groupDelimiter: String,
) : OutputTransformation {
override fun TextFieldBuffer.transformOutput() {
repeat((length - 1) / groupSize) {
insert(it + (it + 1) * groupSize, groupDelimiter)
}
}
}
像这样使用它:
BasicTextField(
...
outputTransformation = GroupingOutputTransformation(4, "-"),
)
它会在每个
-
字符之后插入 4
(末尾除外)。
要在文本字段值之前显示不可修改的前缀,您可以使用OutputTransformation(您可以通过在重复循环后添加insert(0, "ABC-")
来尝试此操作)。虽然乍一看这似乎按预期工作,但光标可以放置在前缀之前。尽管输入的所有字符都将插入到前缀之后,但这可能仍然是不受欢迎的,并且可能会让用户感到困惑。尽管有一个可用于 TextFieldBuffer 的
placeCursorAtEnd()
函数可以解决此问题,但它仅在用于 InputTransformation 时有效,而不是在 OutputTransformation 中使用。
另一种方法是显示文本字段的前缀outside。这可以通过使用 BasicTextField 的
decorator
参数轻松完成:
BasicTextField(
...
decorator = { innerTextField ->
Row {
Text(text = "ABC-", style = TextStyle.Default)
innerTextField()
}
},
)
innerTextField
是您可以根据需要布局的实际文本字段,在本例中位于行中,前面放置了前缀。您需要将相同的 TextStyle 应用于为 BasicTextField 设置的文本(其中使用默认的 TextStyle.Default
),以便它无缝地混合在一起。
@Composable
fun BasicFieldView(
state: TextFieldState,
textStyle: TextStyle = TextStyle.Default,
prefix: String = "ABC-",
maxLength: Int = 12,
groupSize: Int = 4,
groupDelimiter: String = "-",
) {
Column(
modifier = Modifier.fillMaxSize(),
) {
BasicTextField(
state = state,
modifier = Modifier.fillMaxWidth(),
inputTransformation = InputTransformation.maxLength(maxLength)
.then(DigitsOnlyTransformation),
textStyle = textStyle,
outputTransformation = GroupingOutputTransformation(groupSize, groupDelimiter),
decorator = { innerTextField ->
Row {
Text(text = prefix, style = textStyle)
innerTextField()
}
},
)
}
}
常量被提取为具有默认值的参数,以便您可以在需要时轻松调整它们。如您所见,
textFieldValue
onValueChange
参数现在已替换为
state
。正如开头提到的,TextFieldState 是现在使用 TextFields 的现代方式(从即将推出的 1.4.0 版本的
TextField
开始,它也可用于样式化的 Material 3
OutlinedTextField
和
androidx.compose.material3:material3
)。使用 TextFieldState,您不再有 onValueChange 回调,该回调会在内部自动处理。
只需将
rememberTextFieldState()
传递给 BasicFieldView 即可。将其保存在变量中并观察其 text
属性是否发生变化。它是一个 MutableState,因此无论在何处使用,更改都会自动触发重组。或者,您可以在视图模型中使用
val state = TextFieldState()
。如果您想在值更改时触发某些操作,请将其包装在 snapshotFlow 中:
snapshotFlow { state.text }
现在可以在 Compose 外部或 LaunchedEffect 内部收集结果流。