In this post I will show you two different versions of a code entry TextField I created in Android Compose.
The root element used to create these two objects are BasicTextField.
BasicTextField is like a normal TextField but it has more customizable features to it. These elements I will show you take advantage of the decorationBox feature.
Code
First method I will show you with the decoration of each character having a simple underline decoration.
@Composable
fun CodeTextField() {
var text = remember{mutableStateOf("")}
val fixedLength = 6
var lastDigit = rememeber{mutableStateOf(0)}
BasicTextField(
value = text,
onValueChange = {
if (it.length <= fixedLength) {
text = it
lastDigit = text.length
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
decorationBox = {
Row {
repeat(fixedLength) { index ->
Column(
modifier = Modifier
.padding(5.dp)
.drawBehind {
drawLine(
color = Color.Black,
start = Offset(0f, size.height),
end = Offset(size.width, size.height),
strokeWidth = 5f
)
}
.background(
if (lastDigit == index)
Color.Gray.copy(alpha = 0.25f)
else Color.Gray.copy(alpha = 0.05f)
)
) {
Text(
modifier = Modifier.padding(8.dp),
fontSize = 45.sp,
text = if (text.getOrNull(index) == null) " " else text[index].toString()
)
}
}
}
}
)
}
The second one is shown not too different but with a border around each character entered.
@Composable
fun BorderCodeTextField() {
var text = remember{mutableStateOf("")}
val fixedLength = 6
var lastDigit = rememeber{mutableStateOf(0)}
BasicTextField(
value = text,
onValueChange = {
if (it.length <= fixedLength) {
text = it
lastDigit = text.length
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
decorationBox = {
Row {
repeat(fixedLength) { index ->
Column(
modifier = Modifier
.padding(5.dp)
.border(1.dp, Color.Black, RoundedCornerShape(5.dp))
.background(
if (lastDigit == index)
Color.Gray.copy(alpha = 0.25f)
else Color.Gray.copy(alpha = 0.05f)
)
) {
Text(
modifier = Modifier.padding(8.dp),
fontSize = 45.sp,
text = if (text.getOrNull(index) == null) " " else text[index].toString()
)
}
}
}
}
)
}
Below is code I created to add both to the UI along with using the same string input and a callback to update.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SampleCodeEntryTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Column(modifier = Modifier.fillMaxSize().padding(innerPadding), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
var input by remember { mutableStateOf("") }
CodeTextField(input){
input = it
}
Spacer(modifier = Modifier.padding(25.dp))
BorderCodeTextField(input){
input = it
}
}
}
}
}
}
}
@Composable
fun CodeTextField(input : String, callback : (String) -> Unit) {
var text = input
val fixedLength = 6
var lastDigit = text.length
BasicTextField(
value = text,
onValueChange = {
if (it.length <= fixedLength) {
text = it
lastDigit = text.length
callback(text)
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
decorationBox = {
Row {
repeat(fixedLength) { index ->
Column(
modifier = Modifier
.padding(5.dp)
.drawBehind {
drawLine(
color = Color.Black,
start = Offset(0f, size.height),
end = Offset(size.width, size.height),
strokeWidth = 5f
)
}
.background(
if (lastDigit == index)
Color.Gray.copy(alpha = 0.25f)
else Color.Gray.copy(alpha = 0.05f)
)
) {
Text(
modifier = Modifier.padding(8.dp),
fontSize = 45.sp,
text = if (text.getOrNull(index) == null) " " else text[index].toString()
)
}
}
}
}
)
}
@Composable
fun BorderCodeTextField(input : String, callback : (String) -> Unit) {
var text = input
val fixedLength = 6
var lastDigit = text.length
BasicTextField(
value = text,
onValueChange = {
if (it.length <= fixedLength) {
text = it
lastDigit = text.length
callback(text)
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
decorationBox = {
Row {
repeat(fixedLength) { index ->
Column(
modifier = Modifier
.padding(5.dp)
.border(1.dp, Color.Black, RoundedCornerShape(5.dp))
.background(
if (lastDigit == index)
Color.Gray.copy(alpha = 0.25f)
else Color.Gray.copy(alpha = 0.05f)
)
) {
Text(
modifier = Modifier.padding(8.dp),
fontSize = 45.sp,
text = if (text.getOrNull(index) == null) " " else text[index].toString()
)
}
}
}
}
)
}

Hope this was helpful.