En este tutorial aprenderás sobre el uso del RadioButton en Compose. Un control de selección cuyo objetivo es permitir la selección única de un conjunto de opciones.
Claramente el grupo de opciones debe tener dos o más para permitir que el Radio Button provea en la interfaz gráfica un estado de selección, donde su contenedor circular es rellenado por otro circulo cuando se selecciona.
Ejemplo De RadioButton En Compose
Al igual que los demás componentes descritos en la guía de Compose de Develou, puedes encontrar los ejemplos de RadioButtons en repositorio de GitHub:
En el módulo p7_componentes
encontrarás el paquete RadioButton
y cada uno de los archivos Kotlin correspondientes a los encabezados de este tutorial:
Aunque los ejemplos son descritos de forma aislada a lo largo del contenido, todos son integrados en la siguiente interfaz:
La pantalla es construida desde el archivo RadioButtonScreen
.kt, en donde puedes encontrar la creación de la lista y la vinculación a todas las funciones componibles con nombres Example*()
.
Aclarado el propósito de la pantalla de la App, comencemos por aprender a mostrar un RadioButton en pantalla.
1. Crear Un RadioButton
Usa la función componible RadioButton()
de Compose, para desplegar un Radio Button en pantalla. Toma control de su presentación con los siguientes parámetros de su firma:
@Composable
fun RadioButton(
selected: Boolean?,
onClick: (() -> Unit)?,
modifier: Modifier? = Modifier,
enabled: Boolean? = true,
interactionSource: MutableInteractionSource? = remember { MutableInteractionSource() },
colors: RadioButtonColors? = RadioButtonDefaults.colors()
): Unit
Donde los atributos clave tienen como objetivo:
selected
: Booleano que determina si el radio está seleccionado o noonClick
: Tipo función que será invocado cuando el usuario haga clic en el radio buttonenabled
: Representa si el radio button está habilitado o nocolors
: Recibe una instancia deRadioButtonColors
para especificar los colores del componente en cada estado (Ver ejemplo 6)
Aplicando la definición anterior, el ejemplo más básico de un RadioButon
se vería así:
@Composable
fun DevelouRadioButton() {
var isSelected by remember { mutableStateOf(false) }
RadioButton(
selected = isSelected,
onClick = { isSelected = true }
)
}
Como ves, almacenamos el estado de su atributo isSelected
, a fin de sostenerlo a través de las recomposiciones.
Y su valor será modificado desde onClick
para alternar entre los dos estados disponibles.
2. Añadir Etiqueta A Un RadioButton
A diferencia de su equivalente en el sistema de views, la función RadioButton
no trae consigo un parámetro para especificar la etiqueta del mismo.
Por esta razón es necesario crear un layout Row()
donde dispongamos de un elemento Text()
que materialice la etiqueta. Elemento importante, ya que aclara la intención de la opción a seleccionar.
@Composable
fun LabelledRadioButton(
modifier: Modifier = Modifier,
label: String,
selected: Boolean,
onClick: (() -> Unit)?,
enabled: Boolean = true,
colors: RadioButtonColors = RadioButtonDefaults.colors()
) {
Row(
modifier = modifier
.padding(horizontal = 16.dp)
.height(56.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = selected,
onClick = onClick,
enabled = enabled,
colors = colors
)
Text(
text = label,
style = MaterialTheme.typography.body1.merge(),
modifier = Modifier.padding(start = 16.dp)
)
}
}
La función LabelledRadioButton()
anterior, desacopla la etiqueta (label
), el valor del estado actual de selección(selected
) y las acciones de clic (onClick
) para poder reutilizarlo en diferentes contextos.
3. Crear Un Grupo De RadioButtons
El uso de RadioButtons cobra sentido cuando especificamos un conjunto de opciones, de las cuales el usuario puede elegir solo una.
Por ejemplo
Supongamos que deseas permitir al usuario elegir el tipo de animal, al momento de filtrar resultados de productos como se ve en la imagen anterior.
Las opciones disponibles son: Todos, Perros, Gatos y Aves.
¿Cómo resolverlo con un grupo de radio buttons?
- Crea una función que reciba la lista de animales, la opción marcada por defecto y una función a ejecutar cuando se hace clic en el ítem
- Itera sobre la lista de opciones para crear un RadioButton con la opción como etiqueta
- Comunica el cambio de estado al invocador
En código sería:
@Composable
fun RadioGroup(
modifier: Modifier,
items: List<String>,
selection: String,
onItemClick: ((String) -> Unit)
) {
Column(modifier = modifier) {
items.forEach { item ->
LabelledRadioButton(
modifier = Modifier.fillMaxWidth(),
label = item,
selected = item == selection,
onClick = {
onItemClick(item)
}
)
}
}
}
Hemos reutilizado a LabelledRadioButton()
para crear radios con etiquetas. Y como ves, determinamos su estado de selección haciendo la comparación item == selection
y delegamos a su evento de onClick
individual, la ejecución del evento general onItemClick()
.
De esta forma podrás invocar a la función RadioGroup()
con la lista de los tipos de animales y obtener el valor seleccionado:
@Composable
fun Example3() {
val animalTypes = listOf("Todos", "Perro", "Gato", "Aves")
val currentSelection = remember { mutableStateOf(animalTypes.first()) }
RadioGroup(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
items = animalTypes,
selection = currentSelection.value,
onItemClick = { clickedItem ->
currentSelection.value = clickedItem
}
)
}
Cada que el usuario cambie entre opciones, se recibirá la opción seleccionada para incluirla en el filtro de la consulta.
4. Usar El Modificador selectable()
El ejemplo anterior funciona, pero si prestas atención, los RadioButtons son solo seleccionados si el clic es ejercido sobre ellos.
Ese comportamiento nos dirige a la pregunta:
¿Cómo hacer seleccionable a toda la fila?
La respuesta está en el modificador de acción selectable()
, el cual configura a un componente para que haga parte de un grupo de selección mutuamente excluyente.
Su definición es:
fun Modifier?.selectable(
selected: Boolean?,
enabled: Boolean? = true,
role: Role? = null,
onClick: (() -> Unit)?
): Modifier
Donde:
selected
: Determina el estado de selección del componenteenabled
: Si el componente recibirá o no eventosrole
: El tipo de elemento en la interfaz de usuario (para propósitos de accesibilidad)onClick
: Función que se invocará al recibir un clic
Basado en lo anterior, el ejemplo de tipos de animal puede ser ligeramente modificado para añadir el modificador selectable()
sobre la fila del radio button:
@Composable
fun RadioGroupWithSelectable(
modifier: Modifier,
items: List<String>,
selection: String,
onItemClick: ((String) -> Unit)
) {
Column(modifier = modifier.selectableGroup()) { // (1)
items.forEach { item ->
LabelledRadioButton(
modifier = Modifier
.fillMaxWidth()
.selectable( // (2)
selected = item == selection,
onClick = { onItemClick(item) },
role = Role.RadioButton
),
label = item,
selected = item == selection,
onClick = null // (3)
)
}
}
}
Del código anterior:
- Para comunicar apropiadamente a los sistemas de accesibilidad de Android la creación del grupo seleccionable, es importante añadir el modificador
selectableGroup()
al contenedor - Trasladamos el valor de selección y la ejecución de onItemClick en el modificador selectable. Además especificamos Role.RadioButton para ayudar al sistema de accesibilidad
- Asignamos
null
al argumentoonClick
de cadaRadioButton
, ya que no representará un punto de entrada de forma individual
Así, al ejecutar y seleccionar las etiquetas, también marcará al RadioButton correspondiente.
5. Deshabilitar Un RadioButton
Ahora tomemos como ejemplo una App donde deseas ofrecer al usuario diferentes plantillas para sus reportes como se muestra en la imagen anterior.
Las opciones son: Por defecto, Elegante, Audaz y Personalizada.
Debido a que existe una membresía premium, la opción de personalizar plantillas estará deshabilitada si el usuario aún no se ha suscrito.
¿Cómo deshabilitar el RadioButton?
Es claro que debes pasar al atributo enabled
el valor de false
si se cumple la condición de que el usuario no sea premium.
En consecuencia y aunando el anterior criterio, debemos:
- Crear una clase de datos que represente las opciones de plantillas y su estado de disponibilidad
- Crear una función que reciba la lista de opciones de creación de plantillas
- Iterar sobre la lista de opciones y crear una fila con RadioButtons etiquetados con los tipos de plantilla
- Deshabilitar el radio button según en la indicación del invocador
El siguiente código representa las tareas enumeradas:
data class TemplateOption( // (1)
val label: String,
val available: Boolean = true
)
@Composable
fun TemplatesSelector( // (2)
modifier: Modifier,
options: List<TemplateOption>,
selection: TemplateOption,
onItemClick: (TemplateOption) -> Unit,
colors: RadioButtonColors = RadioButtonDefaults.colors()
) {
Column(modifier = modifier) {
options.forEach { option -> // (3)
LabelledRadioButton(
modifier = Modifier
.fillMaxWidth()
.selectable(
selected = option == selection,
onClick = { onItemClick(option) },
role = Role.RadioButton,
enabled = option.available
),
label = option.label,
selected = option == selection,
onClick = null,
enabled = option.available, // (4)
colors = colors
)
}
}
}
Y lo invocamos pasando la lista de opciones y la bandera del estado del usuario:
@Composable
fun Example5() {
/* Este valor es solo para facilitar el ejemplo. Las opciones de plantilla
Deberían venir calculadas */
val isPremium = false
val templateOptions = listOf(
TemplateOption("Por defecto"),
TemplateOption("Elegante"),
TemplateOption("Audaz"),
TemplateOption("Personalizada", isPremium)
)
val currentSelection = remember { mutableStateOf(templateOptions.first()) }
TemplatesSelector(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
options = templateOptions,
selection = currentSelection.value,
onItemClick = { clickedItem ->
currentSelection.value = clickedItem
}
)
}
Al ejecutar podrás ver el grupo de RadioButtons con la desactivación de la opción Personalizada.
6. Cambiar El Color De Un RadioButton
En caso de que desees modificar los colores de un RadioButton, pasa al parámetro colors
una instancia de la interfaz RadioButtonColors
. La forma de fabricarla es con la función RadioButtonDefaults.colors()
:
@Composable
fun colors(
selectedColor: Color? = MaterialTheme.colors.secondary,
unselectedColor: Color? = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
disabledColor: Color? = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
): RadioButtonColors
Tal como se muestra en la firma, podemos proveer colores para los estados de seleccionado, no seleccionado y deshabilitado.
Para ejemplifica, modifiquemos los colores de los radio buttons del ejemplo 5.
Hagamos de cuenta que se establecieron los siguientes valores:
- Seleccionado -> Azul 900
- No Seleccionado -> Azul 500
- Deshabilitado -> Azul 100
Al implementarlo sobre la función RadioButton
, llamamos a RadioButtonDefaults.colors()
con los colores especificados:
@Composable
fun Example6() {
val isPremium = false
val templateOptions = listOf(
TemplateOption("Por defecto"),
TemplateOption("Elegante"),
TemplateOption("Audaz"),
TemplateOption("Personalizada", isPremium)
)
val currentSelection = remember { mutableStateOf(templateOptions.first()) }
TemplatesSelector(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
options = templateOptions,
selection = currentSelection.value,
onItemClick = { clickedItem ->
currentSelection.value = clickedItem
},
colors = RadioButtonDefaults.colors( // <- ¡Cambiando Colores!
selectedColor = Color(0xFF0d47a1),
unselectedColor = Color(0xFF2196f3),
disabledColor = Color(0xFFbbdefb)
)
)
}
Consiguiendo así, el efecto de la ilustración mostrada al inicio de la sección.
Ú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!