En este tutorial aprenderás a usar diálogos en Compose para interrumpir al usuario con una ventana emergente que proyecta información urgente, detalles o confirmación de acciones en tu App Android.
Visualmente, un diálogo está compuesto de un contenedor que actúa como superficie, un título opcional en la parte superior, el texto de apoyo para indicar el objetivo y los respectivos botones de confirmación.
En el Material Design existen varios tipos de diálogos con diferentes propósitos para el usuario. Nuestro objetivo será aprender a implementarlos en Jetpack Compose, procesar los eventos de sus botones de acción y cambiar su estilo.
Ejemplos De Diálogos En Compose
Puedes encontrar el código de este tutorial en el siguiente repositorio GitHub:
El paquete examples/Dialog
del módulo p7_componentes
contiene un archivo por cada cada apartado que se encuentra en este post:
En DialogScreen.kt
encontrarás la pantalla principal donde se ejecutan los ejemplos.
Esta consiste de una función componible cuyo propósito es crear una lista prediseñada en la parte superior. Esta consta de cinco ítems con los nombres de las secciones del tutorial. Y en la parte inferior hay un texto que cambia su contenido según la acción elegida en los diálogos de ejemplo.
Con esto claro, comencemos con el primer apartado, los diálogos de alerta.
1. Diálogo De Alerta
Un diálogo de alerta o Alert Dialog se despliega para interrumpir a los usuarios con información, detalles o acciones que son urgentes en el contexto actual de la App.
Usa una de las dos versiones de la función componible AlertDialog()
para desplegar un diálogo de alerta:
// Versión con botones por defecto
@Composable
fun AlertDialog(
onDismissRequest: (() -> Unit)?,
confirmButton: (@Composable () -> Unit)?,
modifier: Modifier? = Modifier,
dismissButton: (@Composable () -> Unit)? = null,
title: (@Composable () -> Unit)? = null,
text: (@Composable () -> Unit)? = null,
shape: Shape? = MaterialTheme.shapes.medium,
backgroundColor: Color? = MaterialTheme.colors.surface,
contentColor: Color? = contentColorFor(backgroundColor),
properties: DialogProperties? = DialogProperties()
): Unit
// Versión con botones personalizables
@Composable
fun AlertDialog(
onDismissRequest: (() -> Unit)?,
buttons: (@Composable () -> Unit)?,
modifier: Modifier? = Modifier,
title: (@Composable () -> Unit)? = null,
text: (@Composable () -> Unit)? = null,
shape: Shape? = MaterialTheme.shapes.medium,
backgroundColor: Color? = MaterialTheme.colors.surface,
contentColor: Color? = contentColorFor(backgroundColor),
properties: DialogProperties? = DialogProperties()
): Unit
De las firmas anteriores, cada parámetro determina:
onDismissRequest
: Función que se ejecutan cuando el usuario intenta cerrar el diálogo haciendo clic por fuera del mismo o presionando el back buttonconfirmButton
: Botón de confirmación para la acción propuesta por el diálogomodifier
: Modificador para la caja del diálogo que es dibujadadismissButton
: Botón para descartar al diálogotitle
: Texto para el título del diálogotext
: Texto de apoyoshape
: Forma de los bordes del diálogobackgroundColor
: Color de fondocontentColor
: Color aplicado sobre los hijos del diálogo en su sección de contenidoproperties
: Propiedades del diálogo asociadas a la plataformabuttons
: Función componible para crear el layout personalizado de los botones
Veamos un ejemplo de la creación de un diálogo de alerta.
Ejemplo: Diálogo De Eliminación
Supón que el usuario intenta eliminar un registro en la App y tu deseas pedirle una confirmación de esta acción para que no pierda sus datos por equivocación. La siguiente ilustración muestra el diálogo de alerta que cumple este cometido:
Para producir este resultado, crearemos el archivo 01_AlertDialog.kt
y a su vez implementaremos una función componible llamada TutorialAlertDialog()
. En su interior, usaremos el componente AlertDialog
de la siguiente forma:
@Composable
fun TutorialAlertDialog( // (1)
titleText: String? = null,
bodyText: String,
confirmButtonText: String,
onConfirm: () -> Unit,
cancelButtonText: String,
onCancel: () -> Unit,
onDismiss: () -> Unit
) {
// (2)
val title: @Composable (() -> Unit)? = if (titleText.isNullOrBlank())
null
else {
{ Text(text = titleText) }
}
// (3)
AlertDialog(
title = title,
text = {
Text(
text = bodyText
)
},
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = { // (4)
onConfirm()
onDismiss()
}) {
Text(text = confirmButtonText)
}
},
dismissButton = {
TextButton(onClick = { // (5)
onCancel()
onDismiss()
}) {
Text(text = cancelButtonText)
}
}
)
}
Del código anterior:
- Incluimos como parámetros los textos para título, cuerpo y botones. Además de las funciones a ejecutarse al confirmar, cancelar y cerrar
- Ya que
titleText
es opcional, creamos una variabletitle
que tome el valor denull
, en caso de que titleText esté vacío, o la invocación de la funciónText()
para crear el título - Invocamos a la función componible
AlertDialog()
y pasamos los parámetros deTutorialAlertDialog()
- Pudimos haber asignado directamente
onConfirm
al parámetroonClick
delTextButton
, pero como deseamos que el diálogo se cierre, invocamos también aonDismiss
en una lambda multilínea - Al igual que con
confirmButton
,dismissButton
invoca aonDismiss
luego de la acción del botón
Con el componente definido, ahora podemos invocarlo y controlar su estado de visibilidad. Para ello creamos la siguiente función componible Example1()
en DialogScreen.kt
:
@Composable
fun Example1( // (1)
showDialog: (Boolean) -> Unit,
setActionText: (String) -> Unit
) {
TutorialAlertDialog(
bodyText = "¿Eliminar ítem?",
confirmButtonText = "ELIMINAR",
onConfirm = {
setActionText("Ejemplo 1 -> 'ACEPTAR'") // (2)
},
cancelButtonText = "CANCELAR",
onCancel = {
setActionText("Ejemplo 1 -> 'CANCELAR'")
},
onDismiss = { showDialog(false) } // (3)
)
}
Example1
tiene como propósito:
- Recibir la función de asignación del estado de visibilidad del diálogo y el estado del texto de
DialogScreen()
. Ambos serán usados para varios varios parámetros deTutorialAlertDialog()
- Cuando se presiona el botón de confirmación, usamos la forma de invocación de
setActionText
para modificar el texto por un mensaje sobre el botón - El parámetro
onDismiss
toma una lambda donde asignamos false ashowDialog
con el fin de cerrar al diálogo de alerta
Ahora, ¿donde invocamos a Example1
?
La respuesta es: en la función componible que crea las filas de la lista. Es decir, la función SectionRow()
:
@Composable
fun SectionRow(
section: String,
index: Int,
setActionText: (String) -> Unit
) {
// (1)
val (dialogOpen, showDialog) = remember { mutableStateOf(false) }
Box(
modifier = Modifier
.height(48.dp)
.fillMaxWidth()
.clickable(
onClick = {
showDialog(true) // (2)
})
.padding(horizontal = 16.dp),
contentAlignment = Alignment.Center
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = section
)
}
if (!dialogOpen) return // (3)
when (index) {
0 -> Example1(showDialog, setActionText) // (4)
1 -> Example2(dialogOpen, showDialog, setActionText)
2 -> Example3(dialogOpen, showDialog, setActionText)
3 -> Example4(dialogOpen, showDialog, setActionText)
4 -> Example5(dialogOpen, showDialog, setActionText)
}
}
Esta se encarga de crear cada ítem y de manejar la creación de los diálogos:
- Se declara el estado para el diálogo asociado a la fila, usando desestructuración para obtener a
dialogOpen
yshowDialog
. Claramente el valor inicial de la visibilidad esfalse
- Debido a que cada fila tiene un modificador
clickable()
, invocamos al segundo componenteshowDialog()
contrue
para hacer visible al diálogo - Verificamos en cada recomposición si el diálogo es visible, en caso negativo, finalizamos la función con
return
- Si es visible, entonces invocamos la función asociada al índice de la lista. En este caso particular, si es
0
se llama aExample1()
Por último, modifica ComponentsActivity
para que ejecute a DialogScreen
:
class ComponentsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
DialogScreen()
}
}
}
}
}
Ejecuta el módulo p7_componentes
, haz clic en el primer ítem y verás al diálogo de alerta:
Apilar Acciones
Si las etiquetas de las acciones son muy largas para acomodarlas horizontalmente en el contenedor, la función AlertDialog
automáticamente las apilará para mejorar la toma de decisión en un espacio más acorde.
2. Diálogo Simple
Este tipo de diálogo le muestra una lista de elementos al usuario con el fin de aplicar una acción inmediata al seleccionar una opción. Por esta razón no tienen botones de texto, ya que cada elemento de la lista actúa como posible acción.
La única forma de cerrar el diálogo sin realizar ninguna acción es presionando por fuera del mismo.
Veamos cómo implementarlo.
Ejemplo: Diálogo Para Seleccionar Cuentas
(Avatares obtenidos desde uifaces)
Tomemos como ilustración un caso de uso donde deseas cargar los datos de una cuenta de usuario en tu App Android. Debido a que esta permite múltiples usuarios, las cuentas se mostrarán como una lista junto a un ítem para añadirlas como se muestra en la anterior imagen.
¿Cómo implementar este diálogo?
Si revisas la implementación de la función AlertDialog()
, verás que para crear su contenido usa la función Dialog()
. Por lo que podemos hacer exactamente lo mismo para crear nuestro diálogo con lista.
No obstante, usaremos a la segunda variación de AlertDialog()
, que recibe una función componible en su parámetro buttons
, la cual toma todo el espacio del contenedor si no existe texto de apoyo.
Estableciendo lo anterior, ¿Qué debemos hacer?
- Crear una clase de datos que represente las cuentas en la interfaz
- Crear la función componible que represente al diálogo de cuentas
- Crear la función componible que dibuje al contenido del parámetro
buttons
- Crear la función componible para los ítems de cuentas
- Crear la función componible para el ítem de añadir cuenta
Si abres el archivo 02_SimpleDialog.kt
, podrás encontrar las tareas materializadas así:
1. Crear Clase Para Cuentas
La clase Account
representa a los ítems en la lista del diálogo:
data class Account(
val email: String,
@DrawableRes val avatar: Int = R.drawable.no_avatar
)
2. Función AccountsDialog()
Recibe como parámetros una lista de objetos Account
y tres funciones que se ejecutarán al cerrar el diálogo, seleccionar un ítem y añadir una cuenta.
@Composable
fun AccountsDialog(
accounts: List<Account>,
onDismiss: () -> Unit,
onAccountClick: (Account) -> Unit,
onAddAccountClick: () -> Unit
) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = "Seleccionar cuenta",
style = MaterialTheme.typography.h6
)
},
buttons = {
AccountsDialogContent(accounts, onAccountClick, onAddAccountClick)
}
)
}
Como ves, invoca a AlertDialog
y asigna a buttons a la función AccountsDialogContent()
para crear el contenido de contenedor del diálogo.
3. Función AccountsDialogContent()
Con esta función añadimos los ítems de las cuentas y la opción final para añadirlas.
En código tendremos:
@Composable
private fun AccountsDialogContent(
accounts: List<Account>,
onAccountClick: (Account) -> Unit,
onAddAccountClick: () -> Unit
) {
Column {
Spacer(Modifier.height(20.dp))
accounts.forEach { account ->
AccountRow(account, onAccountClick)
}
AddAccountRow(onAddAccountClick)
Spacer(Modifier.height(8.dp))
}
}
Las filas para las cuentas se crean usando la función forEach()
sobre las cuentas. En su interior se invoca a la función AccountRow()
.
Nota: Si el diálogo albergará una gran cantidad de opciones o desconoces el número, lo mejor es usar LazyColumn
(todo).
Y la acción para añadir se proyecta al final con AddAccountRow()
.
4. Función AccountRow()
Las cuentas se tratan de los elementos Image()
y Text()
. La imagen usa en su parámetro painter
el valor del atributo avatar
de la cuenta. Y el texto usa el atributo email
:
@Composable
fun AccountRow(account: Account, onAccountClick: (Account) -> Unit) {
Row(
Modifier
.clickable(onClick = { onAccountClick(account) })
.fillMaxWidth()
.height(56.dp)
.padding(horizontal = 24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier
.clip(CircleShape)
.size(40.dp),
painter = painterResource(id = account.avatar),
contentDescription = "Cuenta de ${account.email}"
)
Spacer(Modifier.width(20.dp))
Text(text = account.email)
}
}
Ambos elementos se encuentran dentro de un layout Row()
con el centrado vertical correspondiente. Adicional pasamos a onAccountClick
como argumento de clickable()
.
5. Función AddAccountRow()
La fila para añadir la cuenta es similar a la de la selección de cuenta, solo que en lugar del avatar se usa un elemento Box()
que recubre un Icon()
y en vez del correo va el texto de la acción:
@Composable
fun AddAccountRow(onAddAccountClick: () -> Unit) {
Row(
modifier = Modifier
.clickable(onClick = onAddAccountClick)
.fillMaxWidth()
.height(56.dp)
.padding(horizontal = 24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(color = Color.LightGray),
contentAlignment = Alignment.Center
) {
Icon(
modifier = Modifier
.clip(CircleShape)
.size(24.dp),
imageVector = Icons.Filled.Add,
contentDescription = "Opción para añadir cuenta"
)
}
Spacer(Modifier.width(20.dp))
Text(text = "Añadir cuenta")
}
}
Ahora, si abres DialogScreen.kt
, verás que el diálogo de cuentas es invocado desde Example2()
:
@Composable
fun Example2(
showDialog: (Boolean) -> Unit,
setActionText: (String) -> Unit
) {
val accounts = listOf(
Account("drjlaw@outlook.com", R.drawable.ac1),
Account("sopwith@sbcglobal.net", R.drawable.ac2),
Account("rmcfarla@att.net", R.drawable.ac3)
)
AccountsDialog(
accounts = accounts,
onAccountClick = { account ->
setActionText("Ejemplo 2 -> '${account.email}'")
showDialog(false)
},
onAddAccountClick = {
setActionText("Ejemplo 2 -> 'Añadir cuenta'")
showDialog(false)
},
onDismiss = { showDialog(false) }
)
}
Se crea una lista de tres cuentas para pasar al parámetro accounts
. Cuando se selecciona una cuenta o intentas añadir una, cambiamos el texto de la pantalla principal con setActionText
.
Al igual que en el primer ejemplo, usamos showDialog
para cerrar el diálogo una vez ocurran los eventos.
Si corres la App y seleccionas el ítem 2, podrás ver el diálogo simple:
3. Diálogo De Confirmación Con Selección Única
Los diálogos de confirmación con selección única, le permiten al usuario elegir una sola opción entre varias. Al igual que el diálogo de alerta, trae consigo dos botones de acción para confirmar o cancelar la selección.
Como notas, es un diálogo con RadioButtons en su contenedor, por lo que es necesario crear una implementación para este diseño.
Veamos.
Ejemplo: Diálogo Para Elegir Formato De Fechas
En este ejemplo asumimos que se requiere mostrar un diálogo con una lista de formatos de fecha, para que el usuario decida cuál prefiere ver en la presentación de datos.
De forma similar al ejemplo de diálogo simple, las tareas se resumen a:
- Crear una función general para el diálogo de confirmación
- Crear una función que dibuje el contenido
- Crear una función para cada fila de RadioButtons
Hagamos un recorrido en el archivo 03_ConfirmationDialog.kt
del repositorio para ver el código de cada función.
1. Función ConfirmationDialog()
Esta función es la encargada de establecer como parámetros los atributos comunes como del diálogo, con el objetivo de pasarlos a llamada de AlertDialog
.
@Composable
fun ConfirmationDialog(
items: List<String>, // (1)
titleText: String,
confirmButtonText: String,
onConfirm: (String) -> Unit,
cancelButtonText: String,
onCancel: () -> Unit,
onDismiss: () -> Unit
) {
val (selectedOption, selectOption) = remember { mutableStateOf(items.first()) } // (2)
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = titleText,
style = MaterialTheme.typography.h6
)
},
buttons = {
ConfirmationDialogContent(
items = items,
selectedOption = selectedOption,
selectOption = selectOption,
confirmButtonText = confirmButtonText,
onConfirm = {
onConfirm(selectedOption) // (3)
},
cancelButtonText = cancelButtonText,
onCancel = onCancel
)
}
)
}
Aspectos a destacar:
- La lista de opciones es de tipo
String
, ya que no aplicaremos lógica adicional cuando se cambie entre selecciones - Además es necesario declarar un estado para sostener la opción seleccionada en las recomposiciones. Por esta razón tenemos a los componentes
selectedOption
yselectOption
- El parámetro
onConfirm
es un tipo función(String)->Unit
porque deseamos comunicar hacia el exterior el valor seleccionado
2. Función ConfirmationDialogContent()
Aquí es donde diseñamos el cuerpo del diálogo, el cual consiste de un elemento Column
con divisores, las opciones y la sección de botones.
@Composable
fun ConfirmationDialogContent(
items: List<String>,
selectedOption: String,
selectOption: (String) -> Unit,
confirmButtonText: String,
onConfirm: () -> Unit,
cancelButtonText: String,
onCancel: () -> Unit
) {
Column(Modifier.selectableGroup()) { // (1)
Spacer(modifier = Modifier.height(20.dp))
Divider()
items.forEach { item ->
ItemRow(
item = item,
selected = item == selectedOption, // (2)
select = selectOption
)
}
Divider()
Row(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(8.dp),
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = onCancel) {
Text(text = cancelButtonText) // (3)
}
Spacer(modifier = Modifier.width(8.dp))
TextButton(onClick = onConfirm) {
Text(text = confirmButtonText) // (3)
}
}
}
}
Detalles de importancia del código anterior:
- Usa el modificador
selectableGroup()
para presentar a la columna como un grupo de elementos seleccionables ante el sistema de accesibilidad - La función creadora de opciones,
ItemRow()
, recibe un booleano que determina si está marcado suRadioButton
. Para inferirlo, comparamos el valor de la iteración con el estado actual enselectedOption
- Los botones reciben directamente los textos y las funciones de a ejecutarse por el parámetro
onClick
3. Función ItemRow()
La función ItemRow()
construye la fila con un RadioButton
y un texto adyacente.
@Composable
fun ItemRow(
item: String,
selected: Boolean,
select: (String) -> Unit
) {
Row(
modifier = Modifier
.selectable(
selected = selected,
onClick = { select(item) },
role = Role.RadioButton
)
.fillMaxWidth()
.padding(start = 24.dp, end = 24.dp)
.height(48.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(selected = selected, onClick = null)
Spacer(modifier = Modifier.width(32.dp))
Text(text = item)
}
}
Como deseamos que toda la fila actué como parte de la selección del radio, le aplicamos el modificador selectable()
.
En el momento en que se toque la fila, invocamos a select
para modificar el estado en ConfirmationDialog
.
Ahora bien, debido a que el RadioButton no será el encargado de ejecutar las acciones de selección, pasamos null
a su parámetro onClick
.
Mostrar Diálogo De Confirmación
La función Example3()
de DialogScreen.kt
esclarece cómo invocar a la función ConfirmationDialog
.
@Composable
fun Example3(
showDialog: (Boolean) -> Unit,
setActionText: (String) -> Unit
) {
val formats = listOf(
"MM/DD/AA",
"DD/MM/AA",
"AA/MM/DD",
"Mes D, AA"
)
ConfirmationDialog(
items = formats,
titleText = "Formato De Fecha",
confirmButtonText = "ACEPTAR",
onConfirm = { format ->
setActionText("Ejemplo 3 -> '$format'")
showDialog(false)
},
cancelButtonText = "CANCELAR",
onCancel = {
setActionText("Ejemplo 3 -> 'CANCELAR'")
showDialog(false)
},
onDismiss = {
showDialog(false)
}
)
}
El ejemplo anterior crea la lista de cuatro formatos de fecha y la pasa a ConfirmationDialog
. Tanto la función de confirmar como la de cancelar, ejecutan un cambio al texto en DialogScreen
.
Al correr y ejecutar la App el resultado será:
4. Diálogo De Confirmación Con Selección Múltiple
En el caso en que desees permitir la selección múltiple, entonces crea el diálogo con CheckBoxes en su contenedor.
Su diseño es exactamente igual al diálogo de selección única, divisores entre el título, una lista de filas y la sección de botones. La única diferencia la notarás en la conservación de múltiples opciones en el estado del componible.
Ejemplo: Diálogo Para Etiquetar
Supongamos que se necesita permitir al editor de un blog asignar etiquetas a los artículos que escribe. Los posibles valores son: Datos, Interfaz Gráfica, Conectividad y Segundo Plano.
Para implementarlo seguimos los mismos pasos del ejemplo anterior. Esto puedes confirmarlo abriendo el archivo 04_MultiChoiceConfirmationDialog.kt
.
1. Función MultiChoiceConfirmationDialog()
Representa al componente del diálogo de confirmación con múltiple selección:
@Composable
fun MultiChoiceConfirmationDialog(
items: List<String>,
titleText: String,
confirmButtonText: String,
onConfirm: (List<String>) -> Unit, // (1)
cancelButtonText: String,
onCancel: () -> Unit,
onDismiss: () -> Unit
) {
val (selectedOptions, selectOptions) = remember {
mutableStateOf(emptyList<String>()) // (2)
}
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = titleText,
style = MaterialTheme.typography.h6
)
},
buttons = {
DialogContent(
items = items,
checkedOptions = selectedOptions,
selectOptions = selectOptions,
onConfirm = { onConfirm(selectedOptions) },// (3)
onCancel = onCancel,
onDismiss = onDismiss,
confirmButtonText = confirmButtonText,
cancelButtonText = cancelButtonText
)
}
)
}
Las instrucciones de esta función son casi iguales a ConfirmationDialog
, pero se diferencian en:
- El parámetro
onConfirm
es tipo(List<String)->Unit
en vista de que notificaremos una lista de varios elementos marcados - En consecuencia del punto anterior, el estado a conservar será una lista de Strings, donde el valor inicial es una lista vacía
- Pasamos una lambda a
DialogContent
con la invocación deonConfirm
sobre las opciones seleccionadas
2. Función DialogContent()
La función para crear el contenido repite la estructura vista en el ejemplo 3, no obstante ciertos elementos cambian:
@Composable
private fun DialogContent(
items: List<String>,
checkedOptions: List<String>,
selectOptions: (List<String>) -> Unit,
confirmButtonText: String,
onConfirm: () -> Unit,
cancelButtonText: String,
onCancel: () -> Unit,
onDismiss: () -> Unit
) {
Column {
Spacer(modifier = Modifier.height(20.dp))
Divider()
items.forEach { currentItem ->
val isChecked = currentItem in checkedOptions // (1)
ItemRow(
item = currentItem,
checked = isChecked,
onValueChange = { checked ->
val checkedItems = checkedOptions.toMutableList()
if (checked)
checkedItems.add(currentItem) // (2)
else
checkedItems.remove(currentItem) // (3)
selectOptions(checkedItems) // (4)
})
}
Divider()
Row(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(8.dp),
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = {
onCancel()
onDismiss() // (5)
}) {
Text(text = cancelButtonText)
}
Spacer(modifier = Modifier.width(8.dp))
TextButton(onClick = {
onConfirm()
onDismiss()
}) {
Text(text = confirmButtonText)
}
}
}
}
En virtud de que manejamos múltiples opciones, debemos:
- Verificar si el ítem actual está marcado en cada recomposición con el operador
in
- Añadir el ítem a la lista de selecciones si está marcado
- Remover el ítem en caso contrario
- Actualizar el estado de elementos seleccionados con el parámetro
selectOptions
- Invocar al parámetro
onDismiss
al final de la lambda asignada al parámetroonClick
de los botones. Este enfoque es válido si sabes que la única acción deonDismiss
es cerrar el diálogo
3. Función ItemRow()
En contraste con la función que produce filas con RadioButtons, ItemRow
presenta un CheckBox
junto a un texto:
@Composable
private fun ItemRow(
item: String,
checked: Boolean,
onValueChange: (Boolean) -> Unit
) {
Row(
modifier = Modifier
.toggleable(
value = checked,
onValueChange = onValueChange,
role = Role.Checkbox
)
.fillMaxWidth()
.padding(start = 24.dp, end = 24.dp)
.height(48.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = checked, onCheckedChange = null)
Spacer(modifier = Modifier.width(32.dp))
Text(text = item)
}
}
Pero esta vez cambiamos al modificador selectable()
por toggleable()
con el objeto de otorgar la capacidad de cambiar entre marcado y desmarcado.
Mostrar Diálogo De Etiquetas
La función Example4()
de DialogScreen.kt
materializa la solución final para mostrar las cuatro etiquetas con las que puede ser marcado un post:
@Composable
fun Example4(
showDialog: (Boolean) -> Unit,
setActionText: (String) -> Unit
) {
val tags = listOf(
"Datos",
"Interfaz Gráfica",
"Conectividad",
"Segundo Plano"
)
MultiChoiceConfirmationDialog(
items = tags,
titleText = "Etiquetar como:",
confirmButtonText = "ACEPTAR",
onConfirm = { selectedTags: List<String> ->
setActionText(
if (selectedTags.isNotEmpty())
"Ejemplo 4 -> ${selectedTags.joinToString()}"
else
"Ejemplo 4 -> 'Sin etiquetas'"
)
},
cancelButtonText = "CANCELAR",
onCancel = {
setActionText("Ejemplo 4 -> 'CANCELAR'")
},
onDismiss = {
showDialog(false)
}
)
}
Si prestas atención a la lambda de onConfirm
, el parámetro selectedTags
representa a la lista de etiquetas seleccionadas cuando se presionó al botón ACEPTAR. Su valor permite imprimir la lista de etiquetas separadas por comas si no está vacío, o el aviso en caso contrario.
Ejecutando el aplicativo con y presionando el ítem cuatro de la lista, el diálogo de las etiquetas aparecerá:
5. Cambiar Estilo De Diálogos
Para terminar estudiemos el cambio de los aspectos visuales a partir de los parámetros de AlertDialog
.
Tomemos como caso el diálogo que aparece en la imagen previa. Se trata de un diálogo de alerta usado como aviso informativo, para que el usuario se de cuenta de que quedan cinco unidades de un producto.
El código para crearlo lo encuentras en el archivo 05_StylingDialogs.kt
.
@Composable
fun StyledAlertDialog(
bodyText: String,
buttonText: String,
onConfirm: () -> Unit,
onDismiss: () -> Unit
) {
AlertDialog(
onDismissRequest = onDismiss,
text = {
Text(bodyText)
},
confirmButton = {
TextButton(
onClick = {
onConfirm()
onDismiss()
},
colors = ButtonDefaults.textButtonColors(contentColor = Color.White)
) {
Text(text = buttonText)
}
},
shape = RoundedCornerShape(
topEndPercent = 50,
bottomStartPercent = 50
),
backgroundColor = Color(0xFF311b92),
contentColor = Color.White
)
}
Con el parámetro shape
pasamos una instancia de RoundedCornerShape
para redondear los bordes superior final e inferior inicial en 50% de la forma original.
El color de la superficie (backgroundColor
) sobre la que se proyecta el diálogo lo establecemos como un tono Deep Purple 900.
Y el color aplicado al texto de soporte (contentColor
) lo establecemos en blanco con Color.White
.
La invocación puedes verla desde Example5()
:
@Composable
fun Example5(
showDialog: (Boolean) -> Unit,
setActionText: (String) -> Unit
) {
StyledAlertDialog(
bodyText = "Quedan solo cinco unidades de este producto.",
buttonText = "Aceptar",
onConfirm = {
setActionText("Ejemplo 5 -> 'ACEPTAR'")
},
onDismiss = {
showDialog(false)
},
)
}
Al ejecutar y seleccionar el quinto ítem de la lista, verás el diálogo personalizado así:
Nota: Asimismo es posible conseguir el mismo resultado, cambiando el tema del diálogo al crear una instancia de MaterialTheme
donde especifiques los colores, formas y tipografía de los elementos en sus diferentes tamaños.
Ú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!