Jetpack Compose单个数字文本字段

16

我正在尝试创建一个电话验证屏幕,用户必须在自己的文本字段中输入5个数字,如下所示:

Verification text field

我有两个问题:

  1. 是否有办法将TextField限制为1个字符。我可以设置单行和最大行数,但是不见得有限制字符长度的方法,就像从视图系统中的“Ms”一样。 我可以轻松地通过忽略第一个字符后面的字符来限制字符长度,但是即使只有1个字符,仍然可以让用户‘向左’和‘向右’滚动。
  2. 是否有办法将宽度包裹到1个字符?目前,我找到的唯一限制宽度的方法是具体设置宽度,但如果系统文本大小更改,则可能会出现问题。

这里是一些代码,希望有所帮助,这是一些非常混乱的解决方案,如果有错误请多包涵:

@Composable
fun CodeTextFields(
    modifier: Modifier = Modifier,
    length: Int = 5,
    onFilled: (code: String) -> Unit
) {
    var code: List<Char> by remember {
        mutableStateOf(listOf())
    }
    val focusRequesters: List<FocusRequester> = remember {
        val temp = mutableListOf<FocusRequester>()
        repeat(length) {
            temp.add(FocusRequester())
        }
        temp
    }

    Row(modifier = modifier) {
        (0 until length).forEach { index ->
            OutlinedTextField(
                modifier = Modifier
                    .weight(1f)
                    .padding(vertical = 2.dp)
                    .focusRequester(focusRequesters[index]),
                textStyle = MaterialTheme.typography.h4.copy(textAlign = TextAlign.Center),
                singleLine = true,
                value = code.getOrNull(index)?.takeIf { it.isDigit() }?.toString() ?: "",
                onValueChange = { value: String ->
                    if (focusRequesters[index].freeFocus()) {   //For some reason this fixes the issue of focusrequestor causing on value changed to call twice
                        val temp = code.toMutableList()
                        if (value == "") {
                            if (temp.size > index) {
                                temp.removeAt(index)
                                code = temp
                                focusRequesters.getOrNull(index - 1)?.requestFocus()
                            }
                        } else {
                            if (code.size > index) {
                                temp[index] = value.getOrNull(0) ?: ' '
                            } else if (value.getOrNull(0)?.isDigit() == true) {
                                temp.add(value.getOrNull(0) ?: ' ')
                                code = temp
                                focusRequesters.getOrNull(index + 1)?.requestFocus() ?: onFilled(
                                    code.joinToString(separator = "")
                                )
                            }
                        }
                    }
                },
                keyboardOptions = KeyboardOptions.Default.copy(
                    keyboardType = KeyboardType.Number,
                    imeAction = ImeAction.Next
                ),

                )
            Spacer(modifier = Modifier.width(16.dp))
        }
    }
}
4个回答

4

要解决这个问题,您可以在BasicTextfield中使用装饰框。

@Composable
fun InputField(
    modifier: Modifier = Modifier,
    text: String = "",
    length: Int = 5,
    keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
    onFocusChanged: () -> Unit = {},
    onTextChange: (String) -> Unit
) {
    val BoxHeight = 40.dp
    val BoxWidth = 40.dp
    var textValue by remember { mutableStateOf(text) }
    val focusRequester = remember { FocusRequester() }
    val focusManager = LocalFocusManager.current
    var isFocused by remember { mutableStateOf(false) }

    if (text.length == length) {
        textValue = text
    }

    BasicTextField(
        modifier = modifier
            .focusRequester(focusRequester)
            .onFocusChanged {
                isFocused = it.isFocused
                onFocusChanged(it)
            },
        value = textValue,
        singleLine = true,
        onValueChange = {
            textValue = it
            if (it.length <= length) {
                onTextChange.invoke(it)
            }
        },
        enabled = enabled,
        keyboardOptions = keyboardOptions,
        decorationBox = {
            Row(Modifier.fillMaxWidth()) {
                repeat(length) { index ->
                    Text(
                        text = textValue,
                        modifier = Modifier
                            .size(
                                width = 
                        BoxWidth,
                                height = BoxHeight
                            )
                            .clip(RoundedCornerShape(4.dp))
                            ,
                    )
                    Spacer(modifier = Modifier.width(8.dp))
                }
            }
        }
    )

    if (acquireFocus && textValue.length != length) {
        focusRequester.requestFocus()
    }
}

这将创建一个包含长度次数(这里是5)的盒子,它只是一个简单的TextField,其装饰为多个框。您可以在viewModel中使用此文本。
TextField示例:https://developer.android.com/jetpack/compose/text here][1]

这个程序的作用是通过长度重复输入的文本。为了使其正常工作,您必须将文本值的每个字符放入Text组件中的每个组件中,而不是将它们全部放在每个组件上。 - AbdulelahAGR
所以类似于您的代码,但在repeat内部,Text(text = textValue.getOrNull(index)?.toString():"",...)将会做到。 - AbdulelahAGR

4

可能有点晚了,但希望这能帮到某些人。

我来这里是为了找解决方案,但发现了@Nikhil的代码,这给了我一个实现的思路(但是失败了)。所以基于他的答案,我改进了可组合性并修复了问题。尚未进行大量测试,但它的行为与您想要的相同:

@Composable
fun DecoratedTextField(
    value: String,
    length: Int,
    modifier: Modifier = Modifier,
    boxWidth: Dp = 38.dp,
    boxHeight: Dp = 38.dp,
    enabled: Boolean = true,
    keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
    keyboardActions: KeyboardActions = KeyboardActions(),
    onValueChange: (String) -> Unit,
) {
    val spaceBetweenBoxes = 8.dp
    BasicTextField(modifier = modifier,
        value = value,
        singleLine = true,
        onValueChange = {
            if (it.length <= length) {
                onValueChange(it)
            }
        },
        enabled = enabled,
        keyboardOptions = keyboardOptions,
        keyboardActions = keyboardActions,
        decorationBox = {
            Row(
                Modifier.size(width = (boxWidth + spaceBetweenBoxes) * length, height = boxHeight),
                horizontalArrangement = Arrangement.spacedBy(spaceBetweenBoxes),
            ) {
                repeat(length) { index ->
                    Box(
                        modifier = Modifier
                            .size(boxWidth, boxHeight)
                            .border(
                                1.dp,
                                color = MaterialTheme.colors.primary,
                                shape = RoundedCornerShape(4.dp)
                            ),
                        contentAlignment = Alignment.Center
                    ) {
                        Text(
                            text = value.getOrNull(index)?.toString() ?: "",
                            textAlign = TextAlign.Center,
                            style = MaterialTheme.typography.h6
                        )
                    }
                }
            }
        })
}

这是它的外观: 演示

您可以根据自己的喜好自定义字体大小和边框颜色。 唯一的缺点是没有光标,无法分别编辑每个框。我会尽力解决这些问题,如果我成功了,我会更新我的答案。


4

要限制只保留一个数字,您可以使用以下方法:

@Composable
fun Field (modifier: Modifier = Modifier,
      onValueChange: (String, String) -> String = { _, new -> new }){

    val state = rememberSaveable { mutableStateOf("") }

    OutlinedTextField(
        modifier = modifier.requiredWidth(75.dp),
        singleLine = true,
        value = state.value,
        onValueChange = {
            val value = onValueChange(state.value, it)
            state.value = value
        },
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Number,
            imeAction = ImeAction.Next),
        )
}

然后使用:
Field(onValueChange = { old, new ->
    if (new.length > 1 || new.any { !it.isDigit() }) old else new
})

enter image description here


0

设置您的键盘选项数字密码类型,请参见下面的代码

keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)

键盘输出下面

enter image description here


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