RadioButton En Compose

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.

Aspecto de Radio Buttons

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:

Ejemplos RadioButton en GitHub
Ejemplos RadioButton en GitHub

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

RadioButtons En Compose
RadioButton En Compose

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 no
  • onClick: Tipo función que será invocado cuando el usuario haga clic en el radio button
  • enabled: Representa si el radio button está habilitado o no
  • colors: Recibe una instancia de RadioButtonColors 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

RadioButton con etiqueta
RadioButton con etiqueta

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

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?

  1. 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
  2. Itera sobre la lista de opciones para crear un RadioButton con la opción como etiqueta
  3. 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 componente
  • enabled: Si el componente recibirá o no eventos
  • role: 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:

  1. 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
  2. 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
  3. Asignamos null al argumento onClick de cada RadioButton, ya que no representará un punto de entrada de forma individual

Así, al ejecutar y seleccionar las etiquetas, también marcará al RadioButton correspondiente.

Item seleccionable con RadioButton
Item seleccionable con RadioButton

5. Deshabilitar Un RadioButton

RadioButton deshabilitado
RadioButton deshabilitado

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:

  1. Crear una clase de datos que represente las opciones de plantillas y su estado de disponibilidad
  2. Crear una función que reciba la lista de opciones de creación de plantillas
  3. Iterar sobre la lista de opciones y crear una fila con RadioButtons etiquetados con los tipos de plantilla
  4. 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.

Opción deshabilitada en el grupo de RadioButtons
Opción deshabilitada en el grupo de RadioButtons

6. Cambiar El Color De Un RadioButton

RadioButtons con colores modificados
RadioButtons con colores modificados

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!