En este tutorial aprenderás sobre el uso del CheckBox en Compose, a fin de crear un componente que permite seleccionar uno o varios ítems de un conjunto. Estos son representados por una casilla que alterna entre dos estados para reflejar la activación/desactivación de la opción en contexto.
Los temas a tratar son:
Ejemplo De CheckBox En Compose
Puedes encontrar todos los ejemplos de este tutorial en el paquete examples/Checkbox
del módulo p7_componentes
, que se encuentra en GitHub.
Verás que existen archivos Kotlin que corresponden numéricamente a las secciones que verás aquí. En ellos encontrarás funciones componibles que puedes seleccionar previsualizar en CheckboxesScreen.kt
.
@Composable
fun CheckboxesScreen() {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
// Reemplaza por el ejemplo que deseas ver
DisabledCheckboxExample()
}
}
@Composable
@Preview
fun CheckboxesScreenPreview() {
MaterialTheme {
Surface {
CheckboxesScreen()
}
}
}
Y si deseas ejecutarlos directamente en el emulador o tu dispositivo, entonces abre ComponentsActivity
y reemplaza la invocación en setContent()
por CheckboxesScreen
:
class ComponentsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
CheckboxesScreen()
}
}
}
}
}
1. Crear Un CheckBox
Usa la función componible Checkbox()
de Compose para dibujar la caja de confirmación en la UI. La firma de esta es:
@Composable
fun Checkbox(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: CheckboxColors = CheckboxDefaults.colors()
): @Composable Unit
Los parámetros de esta función cumplen con los siguientes objetivos:
checked
: Determina si el Checkbox está marcado o noonCheckedChange
: controlador ejecutado cuando el usuario cambia el estadomodifier
: la cadena de modificadores aplicado al Checkboxenabled
: Determina si está activado o desactivadocolors
: Representa los colores de la casilla, la marca y el borde en diferentes estados
Por ejemplo:
El uso más simple de un Checkbox
es activar o desactivar una sola opción. Por lo que solo requerirás de invocar su función con checked
y onCheckedChange
como se muestra en el siguiente ejemplo:
@Composable
fun CheckboxExample() {
val checked = remember { mutableStateOf(true) }
Checkbox(
checked = checked.value,
onCheckedChange = { checked.value = it }
)
}
De forma similar al TextField
, necesitamos recordar el estado de verificación de un Checkbox
en cada recomposición. Por esta razón usamos la variable checked
con la función remember
.
2. Checkbox Con Etiqueta
Para mejorar la intención del Checkbox
es necesario usar una etiqueta de contenido que represente al ítem asociado con marcar o desmarcar.
Así que crearemos una función componible que use un layout Row
para incluir un elemento Text
que sirva como etiqueta para la casilla:
@Composable
fun LabelledCheckbox(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
label: String,
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: CheckboxColors = CheckboxDefaults.colors()
) {
Row(
modifier = modifier.height(48.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = checked,
onCheckedChange = onCheckedChange,
enabled = enabled,
colors = colors
)
Spacer(Modifier.width(32.dp))
Text(label)
}
}
Junto a esta inclusión de la etiqueta también aplicamos state hoisting para desacoplar el estado y el controlador de eventos. De esta forma podremos reutilizar esta función para crear checkboxes con etiqueta en otros lugares.
Por ejemplo:
@Composable
fun CheckboxLabelExample() {
val checked = remember { mutableStateOf(true) }
LabelledCheckbox(
checked = checked.value,
onCheckedChange = { checked.value = it },
label = "Checkbox con etiqueta"
)
}
3. Lista De CheckBoxes
Otro uso es la selección de una o más opciones de una lista de checkboxes. En este caso debes crear la estructura del layout necesaria según el orden y cantidad de ítems que desees mostrar.
Con esto en mente, la siguiente función componible crea el diseño correspondiente:
data class Option( // 1
var checked: Boolean,
var onCheckedChange: (Boolean) -> Unit = {},
val label: String,
var enabled: Boolean = true
)
@Composable
fun CheckboxList(options: List<Option>, listTitle: String) {// 2
Column { // 3
Text(listTitle, textAlign = TextAlign.Justify) // 4
Spacer(Modifier.size(16.dp)) // 5
options.forEach { option -> // 6
LabelledCheckbox( // 7
checked = option.checked,
onCheckedChange = option.onCheckedChange,
label = option.label,
enabled = option.enabled
)
}
}
}
En el código anterior:
- Envolvemos los parámetros del
Checkbox
en una clase de datos llamadaOption
- Creamos una función componible llamada
CheckboxList
que reciba una lista de opciones y el título asociado - Creamos una columna para desplegar verticalmente los elementos
- Mostramos un componente
Text
con el título - Añadimos espacio entre el título y las casillas
- Invocamos la función de extensión
forEach()
desde la lista options con el fin de iterar sobre cada opción - Utilizamos a
LabelledCheckbox()
para desplegar las casillas con su etiqueta correspondiente
Observemos un ejemplo de la función anterior:
Desplegar una conjunto de cuatro disciplinas (Programación, Diseño, Audio y Arte), de las cuales el usuario puede manifestar interés en múltiples de ellas:
@Composable
fun CheckboxListExample() {
val disciplines = listOf("Programación", "Diseño", "Audio", "Arte")
val options = disciplines.map {
val checked = remember { mutableStateOf(false) }
Option(
checked = checked.value,
onCheckedChange = { checked.value = it },
label = it,
)
}
CheckboxList(options = options, listTitle = "Disciplinas")
}
El ejemplo anterior muestra cómo a partir de una lista de strings con las disciplinas se crea un mapeo (map()
)para las opciones que serán parte de la lista.
4. CheckBox Deshabilitado
Usa el atributo enabled
en false
para desactivar un CheckBox a fin de que no reciba eventos del usuario y aparezca en gris (o color que determines para este estado).
Por ejemplo:
Supón que estás creando una app de quizzes y tienes una pregunta donde deseas limitar a la selección de sólo dos ítems en una lista de cuatro:
@Composable
fun DisabledCheckboxExample() {
val gameReleases = listOf( // 1
"Hitman 3",
"Monster Hunter World: Iceborne",
"Days Gone",
"Pokémon Rumble Rush"
)
val options = gameReleases.map { option -> // 2
val checked = remember { mutableStateOf(false) }
Option(
checked = checked.value,
onCheckedChange = { checked.value = it },
label = option
)
}
val numberOfMarks = options.count { it.checked } // 3
if (numberOfMarks == 2) { // 4
options
.filterNot { option -> option.checked }
.forEach { unchecked -> unchecked.enabled = false }
}
CheckboxList( // 5
options = options,
listTitle = "En la siguiente lista hay dos videojuegos lanzados en el 2021." +
" Selecciona tus respuestas:"
)
}
Los pasos de la solución anterior constan de:
- Establecemos las opciones de respuesta en una lista de strings
- Mapeamos los strings en objetos
Option
- Contamos la cantidad de respuestas marcadas en cada recomposición a través de
count()
- Si hay dos selecciones, entonces filtramos aquellas respuestas sin seleccionar y luego las desactivamos
- Terminamos usando nuestra función
CheckboxList
para crear el enunciado de la pregunta junto a las opciones de respuesta
5. CheckBox Indeterminado (TriStateCheckBox)
Compose te permite crear un CheckBox
con un estado adicional llamado estado indeterminado. Este es representado por el componente TriStateCheckbox
.
La siguiente es la firma de su función componible:
@Composable
fun TriStateCheckbox(
state: ToggleableState,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: CheckboxColors = CheckboxDefaults.colors()
): @Composable Unit
Como ves, recibe el parámetro state
para representar su estado a través de la clase enum ToggleableState
, la cual contiene tres valores:
On
: El componente está encendidoOff
: El componente está apagadoIndeterminate
: Estado que representa que los valoresOn
/Off
no pueden ser determinados
El atributo onClick
recibe una lambda para especificar las acciones cuando se hace clic en el Checkbox
, que comúnmente será el cambio de estado.
Por ejemplo:
Crear un TriStateCheckbox
cuyo estado alterne de la forma Off->On->Indeterminate al ser cliqueado:
@Composable
fun TriStateCheckboxExample() {
val state = remember { mutableStateOf(ToggleableState.Off) }
TriStateCheckbox(
state = state.value,
onClick = {
state.value = when (state.value) {
ToggleableState.Off -> ToggleableState.On
ToggleableState.On -> ToggleableState.Indeterminate
ToggleableState.Indeterminate -> ToggleableState.Off
}
}
)
}
6. Relación Padre-Hijo Entre Checkboxes
El TriStateCheckbox
es de utilidad para crear listas de opciones que contienen subselecciones, es decir, Checkboxes con relaciones padre-hijo que facilita la selección o deselección de todos los ítems.
La mezcla de estados en este tipo de relaciones produce los siguientes estados:
- Al seleccionar el padre se seleccionan todos los hijos
- Al deseleccionar al padre se deseleccionan todos los hijos
- A seleccionar algunos hijos, el padre pasa a un estado indeterminado
Con este en mente creemos la función componible CheckboxListWithParent
donde se incluya un TriStateCheckbox
para representar al padre de la lista:
@Composable
fun CheckboxListWithParent(// 1
options: List<Option>,
parentState: ToggleableState,
onParentClick: () -> Unit,
parentLabel: String
) {
Column { // 2
LabelledTriStateCheckbox( // 3
state = parentState,
onClick = onParentClick,
label = parentLabel
)
options.forEach { option -> // 4
LabelledCheckbox( // 5
checked = option.checked,
onCheckedChange = option.onCheckedChange,
label = option.label,
enabled = option.enabled,
modifier = Modifier.padding(start = 32.dp)
)
}
}
}
En el código anterior:
- Tomamos una lista de opciones, el estado actual del padre, el controlador de clic del padre y su etiqueta
- Creamos una columna
- Mostramos en la parte superior al
TriStateCheckbox
pasándole los atributos correspondientes - Usamos de nuevo a
forEach()
para mecanizar el dibujado de los n hijos - Creamos cada casilla con etiqueta con
padding
en el inicio para conseguir una sangría que marque la diferencia entre padre e hijos
Veamos un ejemplo de la función anterior:
Considera la creación de la sección de un filtro que permite seleccionar marcas de autos:
@Composable
fun CheckboxListWithParentExample() {
val labels = listOf(// 1
"Interbrand",
"Toyota",
"Mercedes Benz",
"BMW",
"Honda",
"Hyundai",
"Tesla",
"Ford"
)
val childStates = remember { // 2
val elements = Array(labels.size) { false }
mutableStateListOf(*elements)
}
val options = labels.mapIndexed { index, label -> // 3
Option(
checked = childStates[index],
onCheckedChange = { childStates[index] = it },
label = label,
)
}
val parentState = remember(*childStates.toTypedArray()) { // 4
when {
childStates.all { it } -> ToggleableState.On
childStates.none { it } -> ToggleableState.Off
else -> ToggleableState.Indeterminate
}
}
val onParentClick = { // 5
val derivedState = parentState != ToggleableState.On
childStates.fill(derivedState)
}
CheckboxListWithParent( // 6
options = options,
parentState = parentState,
onParentClick = onParentClick,
parentLabel = "Marcas"
)
}
En el código anterior el algoritmo cumple con:
- Partimos de una lista de strings en memoria con las marcas de autos disponibles para el filtro
- Creamos un Array de booleanos inicializados en false con el tamaño de la lista de marcas. Luego creamos una lista de estados con
mutableStateListOf()
que memorice a cada elemento del array, por lo que usamos el operador Spread (*
) para satisfacer los argumentos variables - Creamos una lista de opciones con el mapeo indexado de las etiquetas
- Determinamos el estado del padre con
remember()
junto a los valores de los estados de los hijos. Esto hace que en cada recomposición se recalcule el estado del padre si los estados de los hijos cambiaron. Claramente, si todos los hijos están marcados (all()
), el estado del padre seráOn
. Si ninguno está marcado (none()
), seráOff
. Y cualquier otro caso seráIndeterminate
- Actualizamos los estados de todos los hijos cuando se hace clic en el padre. Por lo que usamos la función
fill()
para asignar el mismo valor a todos los elementos dechildStates
cuando se obtienederivedState
- Finalizamos creando la lista de checkboxes con relación padre-hijo
7. Cambiar Colores De CheckBox
El atributo colors
recibe una instancia del tipo CheckBoxColors
para asignar elementos Color a diferentes partes decorativas del CheckBox. Puedes crear una instancia con la función CheckBoxDefaults.colors()
y pasar los siguientes parámetros:
@Composable
fun colors(
checkedColor: Color = MaterialTheme.colors.secondary,
uncheckedColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
checkmarkColor: Color = MaterialTheme.colors.surface,
disabledColor: Color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled),
disabledIndeterminateColor: Color = checkedColor.copy(alpha = ContentAlpha.disabled)
): @Composable CheckboxColors
Donde:
checkedColor
: Es el color usado en el borde y el fondo de la casilla cuando está marcadouncheckedColor
: Es el color usado en el borde cuando está desmarcadocheckmarkColor
: Color de la marca de verificacióndisabledColor
: Color del borde y casilla cuando está desactivadodisabledIndeterminateColor
: Color del borde y casilla cuando elTriStateCheckBox
(ver última sección) está en estado indeterminado
Por ejemplo:
Cambiar los colores de un Checkbox con las siguientes requerimientos:
- Color de selección: Púrpura 100
- Color de marca: Púrpura 900
- Color deselección: Púrpura 400
- Color deshabilitado: Gris claro
- Color deshabilitado + estado indeterminado: Color de selección con alpha de contraste bajo
Basado en la lista anterior creemos una función componible que muestre 5 checkboxes en una columna junto a la etiqueta que especifique su estado actual:
@Composable
@Preview
fun ColoredCheckboxExample() {
val checkedColor = Color(0xFFE1BEE7)
val checkmarkColor = Color(0xFF4A148C)
val uncheckedColor = Color(0xFF7E57C2)
val disabledColor = Color.LightGray
val disabledIndeterminateColor = checkedColor.copy(ContentAlpha.disabled)
Column {
LabelledCheckbox(
label = "Enabled+Selected",
checked = true,
onCheckedChange = { },
colors = CheckboxDefaults.colors(
checkedColor = checkedColor,
checkmarkColor = checkmarkColor,
)
)
LabelledCheckbox(
label = "Enabled+Unselected",
checked = false,
onCheckedChange = { },
colors = CheckboxDefaults.colors(
uncheckedColor = uncheckedColor,
)
)
LabelledCheckbox(
label = "Disabled+Unselected",
checked = false,
onCheckedChange = { },
colors = CheckboxDefaults.colors(
disabledColor = disabledColor,
),
enabled = false
)
LabelledCheckbox(
label = "Disabled+Selected",
checked = true,
onCheckedChange = { },
colors = CheckboxDefaults.colors(
disabledColor = disabledColor,
),
enabled = false
)
LabelledTriStateCheckbox(
label = "Disabled+Indeterminate",
state = ToggleableState.Indeterminate,
onClick = { },
enabled = false,
colors = CheckboxDefaults.colors(
disabledIndeterminateColor = disabledIndeterminateColor
)
)
}
}
El resultado del código anterior es el siguiente:
Únete Al Discord De Develou
Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!