Variables
val requesterList: ArrayList<FocusRequester> = arrayListOf()
val textList: ArrayList<MutableState<TextFieldValue>> = arrayListOf()
var lastIndex = -1
for (i in 0 until count){
// Add string mutableStateOf variables to the input count
textList.add(
remember {
mutableStateOf(
TextFieldValue(
text = "",
selection = TextRange(0)
)
)
}
)
// Add FocusRequester object to the input count
requesterList.add(FocusRequester())
}
Generate OtpView
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
Row {
for (i in textList.indices) {
InputView(
value = textList[i].value,
modifier = Modifier
.onFocusChanged {
// Focus on the priority cell when click in otp viewer
if (it.isFocused && lastIndex != i) {
lastIndex = i
setFocus(textList, requesterList)
}
}
.focusRequester(requesterList[i])
.onKeyEvent {
if (it.key == Key.Backspace) {
// Go to previous cell after clicking backspace
preFocus(i, textList, requesterList)
onValueChange(otp(textList))
true
} else {
false
}
}
) { newValue ->
if (newValue.text != "" && textList[i].value.text == "") {
textList[i].value = TextFieldValue(
text = newValue.text,
selection = TextRange(1)
)
// Go to next cell after type one digit
nextFocus(textList, requesterList)
}
onValueChange(otp(textList))
}
}
}
}
Otp cell composable
@Composable
fun InputView(
modifier: Modifier,
value: TextFieldValue,
onValueChange: (value: TextFieldValue) -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.padding(8.dp)
.width(48.dp)
.height(48.dp)
.border(
width = 1.dp,
color = Color.Gray,
shape = RoundedCornerShape(8.dp),
),
) {
BasicTextField(
readOnly = false,
value = value,
modifier = modifier.clip(RoundedCornerShape(8.dp)),
onValueChange = onValueChange,
enabled = true,
maxLines = 1,
textStyle = MaterialTheme.typography.h4.copy(textAlign = TextAlign.Center),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = null
)
)
}
}
merge otp string function
fun otp(textList: List<MutableState<TextFieldValue>>): String {
var otp = ""
for (index in textList.indices) {
otp += textList[index].value.text
}
return otp
}
Focus on the priority cell function
fun setFocus(
textList: List<MutableState<TextFieldValue>>,
requesterList: List<FocusRequester>,
) {
var founded = -1
for (index in textList.indices) {
if (textList[index].value.text == "") {
requesterList[index].requestFocus()
founded = index
break
}
}
if (founded != -1)
requesterList[founded].requestFocus()
else
requesterList[requesterList.size - 1].requestFocus()
}
Go to next cell function
fun nextFocus(
textList: List<MutableState<TextFieldValue>>,
requesterList: List<FocusRequester>,
) {
for (index in textList.indices) {
if (textList[index].value.text == "") {
if (index < textList.size) {
requesterList[index].requestFocus()
}
break
}
}
}
Go to pre cell function
fun preFocus(
index: Int,
textList: List<MutableState<TextFieldValue>>,
requesterList: List<FocusRequester>,
) {
if (index != 0 || textList[0].value.text != "")
if (index in textList.indices && textList[index].value.text != "")
textList[index].value = TextFieldValue(
text = "",
selection = TextRange(0)
)
else if (index in 1..textList.size - 2 || textList[index].value.text == "") {
textList[index - 1].value = TextFieldValue(
text = "",
selection = TextRange(0)
)
requesterList[index - 1].requestFocus()
}
}