Una parte importante al explorar cualquier lenguaje de programación en la parte de diseño de interfaces, es el diseño de formularios, en este artículo les explicaré cómo diseñar formularios en Android, específicamente usando Kotlin, una alternativa considero más sencilla que Java.
1. Creación del proyecto
Para la creación del proyecto, sencillamente nos vamos a crear una Empty Activity, para efectos de este ejemplo Prueba, seleccionando el lenguaje Kotlin, esperamos a que se genere el proyecto, que todas las barras de progreso se hayan llenado, y modificamos el class MainActivity.
[MainActivity.kt]
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
PruebaTheme {
Scaffold(modifier = Modifier.fillMaxSize()) {
innerPadding -> AppContent(
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
En seguida modificamos también las funciones que vienen abajo de la clase.
[MainActivity.kt]
...
@Composable
fun AppContent(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.padding(24.dp),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Center
) {
// Aquí irán nuestras cajas de texto y botones.
}
}
@Preview(showBackground = true)
@Composable
fun AppContentPreview() {
PruebaTheme {
AppContent()
}
}
Explico un poco el código de la función AppContent, para ser precisos, el método Column, el cual recibe como parámetros un modifier, el cuál permitirá con fillMaxSize() generar una vista previa completa y con padding() añadirá un relleno a la pantalla, seguido del modifier tendremos la alineación horizontal y vertical.
Antes de pasar al siguiente paso, prestamos atención en el comentario el cual he colocado justo en la primera función AppContent, ya que próximas secciones de código harán referencia a este apartado.
2. Diseño básico
Comencemos con un diseño básico, un inicio de sesión, definimos e inicializamos una constante para el contexto de los mensajesToast y dos variables para almacenar el valor de los TextFields, a las mencionadas les he colocado un comentario para entender su finalidad, dentro del Column pongamos una etiqueta principal que nos indique de qué trata el formulario, una etiqueta sencilla que nos indique la caja de texto de usuario con esta misma y otra etiqueta siguiendo el patrón para la contraseña.
[MainActivity.kt]
...
fun AppContent(...) {
val context = LocalContext.current
var usuario: String by remember { mutableStateOf("") }
var contrasena: String by remember { mutableStateOf("") }
Column(
...
) {
Text(
text = "Inicio de Sesión",
fontSize = 20.sp,
fontWeight = FontWeight.ExtraBold,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Usuario:")
TextField(
value = usuario,
onValueChange = { usuario = it },
placeholder = { Text("Ingresa tu usuario") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Contraseña:")
TextField(
value = contrasena,
onValueChange = { contrasena = it },
placeholder = { Text("Ingresa tu contraseña") },
modifier = Modifier.fillMaxWidth(),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = {
Toast.makeText(context, "Usuario: ${usuario}", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Contraseña: ${contrasena}", Toast.LENGTH_SHORT).show()
},
modifier = Modifier.align(Alignment.End)
) {
Text("Iniciar sesión")
}
}
}
En la vista previa deberemos apreciar un resultado parecido al de abajo, a la imagen la he editado para señalar los elementos destacados, el modo Split para apreciar tanto código como vista previa, el elemento Button anida un Text por eso vemos que es señalado 2 veces, un elemento que pasa por desapercibido es el Spacer, pero no es más que un salto de línea, en general vemos como ciertos elementos reciben parámetros, para personalizar su estilo, resalta que el Button tiene un onClick que como el mismo nombre lo indica, vincula un evento de clic, en este caso envía un mensaje toast para apreciar que valores hay en las cajas de texto del formulario.

3. Tipos de TextFields y Selectboxes
El TextField ordinario aceptará cualquier texto, aprovecho hacer mención que en el código previo había una característica del TextField, visualTransformation = PasswordVisualTransformation() para lograr el tipo contraseña, a continuación creamos un formulario adicional al inicio de sesión, definimos e inicializamos un data class, una constante de opciones y dos variables, una para almacenar el estado y la otra para el valor del selectbox, tres variables para almacenar los valores de los otros TextFields, el primer TextField tiene el particular en el diseño la extensión al Modifier.height(), las propiedades maxLines y singleLine, los otros dos TextFields cuenta con la propiedad keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number).
[MainActivity.kt]
...
fun AppContent(...) {
...
data class Opcion(val value: String, val label: String)
val productoOpciones = listOf(
Opcion("1", "Sponch Fresa"),
Opcion("2", "Emperador Combinado"),
Opcion("3", "Florentinas Cajeta")
)
var productoExpandido by remember { mutableStateOf(false) }
var producto by remember { mutableStateOf(productoOpciones[0]) }
var comentarios: String by remember { mutableStateOf(("")) }
var precio: String by remember { mutableStateOf(("")) }
var cantidad: String by remember { mutableStateOf(("")) }
Column(
...
) {
...
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Producto:")
ExposedDropdownMenuBox(
expanded = productoExpandido,
onExpandedChange = { productoExpandido = !productoExpandido }
) {
TextField(
value = producto.label,
onValueChange = {},
readOnly = true,
label = { Text("Selecciona una opción") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = productoExpandido)
},
modifier = Modifier
.fillMaxWidth()
.menuAnchor()
)
DropdownMenu(
expanded = productoExpandido,
onDismissRequest = { productoExpandido = false }
) {
productoOpciones.forEach { opcion ->
DropdownMenuItem(
text = { Text(opcion.label) },
onClick = {
producto = opcion
productoExpandido = false
}
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Comentarios:")
TextField(
value = comentarios,
onValueChange = { comentarios = it },
label = { Text("Ingresa comentarios") },
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
maxLines = 3,
singleLine = false
)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Precio:")
TextField(
value = precio,
onValueChange = { precio = it },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Cantidad:")
TextField(
value = cantidad,
onValueChange = { cantidad = it },
placeholder = { Text("Ingresa la cantidad") },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
Toast.makeText(context, "Producto Id: ${producto.value}", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Producto: ${producto.label}", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Comentarios: ${comentarios}", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Precio: ${precio}", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Cantidad: ${cantidad}", Toast.LENGTH_SHORT).show()
},
modifier = Modifier.align(Alignment.End)
) {
Text("Enviar")
}
}
}
El preview resultado:

Conclusión
El artículo ha llegado a su fin, sin embargo, puede que conforme pase el tiempo pueda añadir algún otro formulario práctico que incluya otros elementos típicos. De momento espero este post les sea de utilidad y estaré al tanto de cualquier comentario, gracias por su tiempo.