Ejemplo práctico 9: Menú con DropdownMenu en BottomAppBar
Objetivo
Este ejemplo es una variante del anterior. Se sustituirá la TopAppBar
con un menú por una BottomAppBar
, evaluando la selección del usuario mediante un único callback, comprobando la respuesta recibida y actuando en consecuencia. Debes tener en cuenta que en Compose los métodos no devuelven valores, de ahí el uso de callbacks. Se mantendrá la misma estructura de sealed class
, añadiendo label
e icon
.
Recursos en string.xml
Como en la versión anterior, se tratará de evitar lo máximo posible el hardcoded text, aunque en ocasiones, verás que se omite por razones didácticas.
1<resources>
2 <string name="app_name">ExampleT2_8</string>
3
4 <string name="txt_welcome">Selecciona una opción del menú</string>
5
6 <string name="txt_option_title">Más opciones</string>
7
8 <string name="txt_option_share">Compartir</string>
9 <string name="txt_option_save">Guardar</string>
10 <string name="txt_option_logout">Cerrar sesión</string>
11
12 <string name="txt_share">Has seleccionado la opción <b>Compartir</b>.</string>
13 <string name="txt_save">Has seleccionado la opción <b>Guardar</b>.</string>
14 <string name="txt_logout">Has seleccionado la opción <b>Cerrar sesión</b>.</string>
15</resources>
Sealed Class
Para simplificar el código, se creará la siguiente sealed class
en un fichero a parte, lo que permite reducir las evaluaciones para este caso. Como se ha comentado, se añadirán dos nuevas propiedades a la clase label
e icon
.
1sealed class OpcionMenu(val label: String, val icon: ImageVector) {
2 object Compartir : OpcionMenu("Compartir", Icons.Default.Share)
3 object Guardar : OpcionMenu("Guardar", Icons.Default.Add)
4 object Logout : OpcionMenu("Cerrar sesión", Icons.AutoMirrored.Filled.ExitToApp)
5
6 companion object {
7 val todas = listOf(Compartir, Guardar, Logout)
8 }
9}
Compose BottomAppBarConMenu
Supón que quieres reutilizar este componente en más de una vista, para eso se creará este componente en un fichero separado, por ejemplo, Utils.kt
. Esta versión está mejorada con respecto al ejemplo anterior, se crea un bucle para mostrar las opciones que se vayan añadiendo en la sealed class
.
1@Composable
2fun BottomAppBarConMenu(
3 onOpcionSeleccionada: (OpcionMenu) -> Unit
4) {
5 var expanded by remember { mutableStateOf(false) }
6
7 BottomAppBar(
8 actions = {
9 IconButton(onClick = { expanded = !expanded }) {
10 Icon(Icons.Default.MoreVert, contentDescription = "Menú inferior")
11 }
12
13 DropdownMenu(
14 expanded = expanded,
15 onDismissRequest = { expanded = false }
16 ) {
17 OpcionMenu.todas.forEach { opcion ->
18 DropdownMenuItem(
19 text = { Text(opcion.label) },
20 leadingIcon = { Icon(opcion.icon, contentDescription = null) },
21 onClick = {
22 expanded = false
23 onOpcionSeleccionada(opcion)
24 }
25 )
26 }
27 }
28 }
29 )
30}
Resultado de la MainActivity
Ahora, la actividad principal tendrá un aspecto más limpio al hacer uso de la sealed class y el componente creado en un fichero a parte.
1class MainActivity : ComponentActivity() {
2 override fun onCreate(savedInstanceState: Bundle?) {
3 super.onCreate(savedInstanceState)
4 enableEdgeToEdge()
5 setContent {
6 ExampleT2_9Theme {
7 PantallaPrincipal()
8 }
9 }
10 }
11}
12
13@Preview(showBackground = true)
14@Composable
15fun PantallaPrincipal() {
16 val context = LocalContext.current
17 var mensaje by remember { mutableStateOf(context.getString(R.string.txt_welcome)) }
18
19 Scaffold(
20 bottomBar = {
21 BottomAppBarConMenu { opcion ->
22 mensaje = when (opcion) {
23 is OpcionMenu.Compartir -> context.getString(R.string.txt_share)
24 is OpcionMenu.Guardar -> context.getString(R.string.txt_save)
25 is OpcionMenu.Logout -> context.getString(R.string.txt_logout)
26 }
27 }
28 },
29 modifier = Modifier.fillMaxSize()
30 ) { innerPadding ->
31 Box(
32 modifier = Modifier
33 .fillMaxSize()
34 .padding(innerPadding),
35 contentAlignment = Alignment.Center
36 ) {
37 Text(mensaje)
38 }
39 }
40}
Como puedes ver, BottomAppBar
permite el uso de actions
igual que la TopAppBar
. También es posible implementar este menú utilizando la sección floatingActionButton
y utilizando FloatingActionButton
, y se puede combinar ambas barras (topBar
y bottomBar
) en el mismo Scaffold
.