Menús Dropdown de opciones en Android Studio | Jetpack Compose

Video thumbnail

Los menús son componentes de la Interfaz de Usuario claves para cualquier sistema; en Android los menús presentan opciones que permite realizar acciones como desplazarnos entre las distintas Actividades que componen una aplicación Android.

Anteriormente vimos como crear un sistemas de Tabs con TabRow HorizontalPager.

Crear un menú con Jetpack Compose (forma moderna)

Con la llegada de Jetpack Compose, la forma de crear interfaces de usuario en Android ha cambiado significativamente. Jetpack Compose utiliza un enfoque declarativo para construir interfaces, lo que significa que describes cómo debería ser tu UI en un momento dado, y el framework se encarga de actualizarla cuando los datos cambian.

La forma que hemos visto anteriormente utilizando XML se considera ahora el enfoque "Legacy". Aunque sigue siendo funcional, Google recomienda utilizar Jetpack Compose para los nuevos desarrollos.

Otro componente que no puede faltar, y que a pesar de ser un clásico sigue siendo sumamente útil, es el menú tradicional (no el Navigation Drawer, sino el menú de toda la vida). Veremos dos implementaciones comunes: una en el TopAppBar y otra dentro de un ítem de una lista, como un botón de opciones.

Creación del Composable Reutilizable

Para este ejemplo, vamos a crear un componente @Composable llamado MyMenu. Para hacerlo flexible, este recibirá como parámetros las funciones que se ejecutarán al hacer clic en las opciones de editar y eliminar:

@Composable
fun MyMenu(
    onEdit: () -> Unit,    // Acción para editar
    onDelete: () -> Unit   // Acción para eliminar
) {
    var expanded by remember { mutableStateOf(false) }

    Box(modifier = Modifier.wrapContentSize(Alignment.TopEnd)) {
        IconButton(onClick = { expanded = true }) {
            Icon(Icons.Default.MoreVert, contentDescription = "Menú")
        }

        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false }
        ) {
            DropdownMenuItem(
                text = { Text("Editar") },
                leadingIcon = { Icon(Icons.Default.Edit, contentDescription = null) },
                onClick = {
                    expanded = false // Cerramos el menú
                    onEdit()        // Ejecutamos la función que nos pasaron
                }
            )
            DropdownMenuItem(
                text = { Text("Eliminar") },
                leadingIcon = { Icon(Icons.Default.Delete, contentDescription = null) },
                onClick = {
                    expanded = false
                    onDelete()
                }
            )
        }
    }
}

Estructura del DropdownMenu

El DropdownMenu es el contenedor (wrapper) que envuelve a los ítems. Su funcionamiento se basa en la reactividad de Compose:

  • expanded: Es el estado booleano que determina si el menú se muestra. Usamos remember y mutableStateOf para que Compose sepa cuándo redibujar.
  • onDismissRequest: Se dispara cuando el usuario pulsa fuera del menú o presiona "atrás". En este caso, simplemente ponemos expanded = false.

Los DropdownMenuItem. En este ejemplo:

  • Usamos un IconButton para mostrar el ícono del menú (tres puntos verticales).
  • El estado expanded controla si el menú está visible o no.
  • DropdownMenu es el composable que muestra la lista de opciones.
  • DropdownMenuItem define cada una de las opciones del menú.
  • text: El nombre de la acción (ej. "Editar").
  • leadingIcon: Un icono representativo a la izquierda.
  • onClick: La acción a ejecutar. Es vital que, además de llamar a la función (como onEdit()), volvamos a poner expanded = false para que el menú se cierre tras la selección.

Este enfoque moderno es más intuitivo y se integra mejor con el resto de tu aplicación si estás utilizando Jetpack Compose.

Integración en la Interfaz

Una vez creado nuestro menú, podemos emplearlo en distintas partes de la aplicación:

En el TopAppBar

Podemos pasarlo dentro de las actions del Scaffold para que aparezca en la parte superior derecha de la pantalla:

Scaffold(modifier = Modifier.fillMaxSize(),

    topBar = {
        TopAppBar(
            title = { Text("Mi Aplicación") },
            actions = {
                // Simplemente llamas a tu función aquí
                MyMenu(
                    onEdit = { println("¡Abriendo editor!") },
                    onDelete = { println("¡Borrando elemento!") }
                )
            }
        )
    }

En un Ítem de Lista (TaskItem)

Como mencionamos, también lo hemos incluido en un componente llamado TaskItem. Aquí, el menú se coloca dentro de un Row junto con el texto de la tarea. Hemos ajustado el VerticalAlignment para que el icono de los tres puntos quede perfectamente centrado respecto al contenido:

Scaffold(modifier = Modifier.fillMaxSize(),

    topBar = {
        TopAppBar(
            title = { Text("Mi Aplicación") },
            actions = {
                // Simplemente llamas a tu función aquí
                MyMenu(
                    onEdit = { println("¡Abriendo editor!") },
                    onDelete = { println("¡Borrando elemento!") }
                )
            }
        )
    }
) { innerPadding ->
    Column() {
        Greeting(
            name = "Android",
            modifier = Modifier.padding(innerPadding)
        )
        TaskItem()
    }
}

@Composable
fun TaskItem() {
    Card(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
        Row(
            modifier = Modifier.padding(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(text = "Test", modifier = Modifier.weight(1f))

            // El menú aparecerá pegado al icono dentro de la tarjeta
            MyMenu(
                onEdit = { println("¡Abriendo editor!") },
                onDelete = { println("¡Borrando elemento!") }
            )
        }
    }
}

Nota sobre los parámetros de función

Fíjate que en la definición usamos () -> Unit. Esto significa que el parámetro es una función que no recibe nada y no devuelve nada (Unit). Al llamar al componente, pasamos la lógica entre llaves: 

MyMenu(onEdit = { /* lógica de edición */ }, onDelete = { /* lógica de borrado */ }).

Desplegar un menú en Android

El menú de las aplicaciones Android se despliegan al presionar el icono correspondiente al menu ubicado generalmente en la esquina superior derecha de la pantalla:

mEnú android

En esta entrada veremos cómo crear un menú de opciones básico para Android

Formas de crear un menú en Android

Históricamente, existían dos formas para crear un menú de opciones en Android: a través de un archivo XML (la forma "Legacy" que detallaremos primero) y mediante código. Sin embargo, con la modernización del desarrollo de Android, la forma recomendada es usar Jetpack Compose y sus funciones "composables". En esta entrada, cubriremos el enfoque Legacy basado en XML y luego presentaremos la nueva forma con Jetpack Compose.

Definir un menú basado en XML

Para todos los tipos de menús, Android proporciona un formato en XML para definir los elementos del mismo en lugar de construir un menú con código en una actividad; crear un menú de esta forma tiene como ventaja principal que es más sencillo visualizar su estructura y evita crear elementos visuales desde la vista al encontrarse separado la definición del menú en un archivo aparte del código principal de la aplicación.

Elementos de un menú en XML

  • <menu>: Es el contenedor de los elementos de un menú; podemos decir que es el que define a un menú; por lo tanto debe ser el nodo raíz y puede contener uno o más de los siguientes elementos:
    • <item>: Representa un elemento del menú; puede contener un elemento menú para hacer un sub-menú.
    • <group>: (opcional) Simplemente nos permite categorizar nuestros elementos del menú (<item>); por lo tanto solo puede contener <item> dentro de el.

Atributos de los elementos del menú (<item>)

  • <android:id>: Como cualquier elemento; es un identificador de un recurso único dentro de nuestro menú; el cual permite a la aplicación reconocerlo cuando el usuario pulsa el mismo.
  • <android:title>: El título del menú; y será el texto que se refleje en el menú.
  • <android:icon>: Colocarle un icono al elemento; los iconos utilizados deberán estar en las carpetas: res\drawable
  • <android:showAsAction>:

Especifica cuando y como debe de aparecer el elemento del menú (<item>).

Ejemplo de un menú básico en Android

Una vez visto como esta formado un menú; podemos crear un menú de ejemplo básico; el cual estará conformado por tres opciones:

  • Opcion 1
  • Opcion 2
  • Opcion 3

Y tendrá el siguiente aspecto:

menu android

Creando el menú de opciones en Android

Para crear el menú anterior mediante su definición en XML debemos de crear un XML que llamaremos "menu.xml" ubicado en el directorio:

res\menu

Y agregamos el siguiente contenido:

<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/opcion1" android:title="Opcion1"></item>
<item android:id="@+id/opcion2" android:title="Opcion2"></item>
<item android:id="@+id/opcion3" android:title="Opcion3"></item>
</menu>

El menú <menu> esta formado <item>; cada uno de ellos con un identificador android:id único y un titulo android:title.

Por último debemos de sobreescribir los métodos onCreateOptionsMenu y onOptionsItemSelected en nuestro Activity:

El método onCreateOptionsMenu

  @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }

El método onCreateOptionsMenu dentro del actividad que implemente el menú permite inflar (convertir un recurso XML, en nuestro caso el menú en un objeto, en este caso un Menú) el XML que define el menú mediante R.menu.menu que se corresponde con el XML que definimos en los pasos anteriores y que está ubicado en el directorio res/menu; retornamos true para mostrar el menú.

El método onMenuItemSelected

@Override 
  public boolean onMenuItemSelected(int featureId, MenuItem item) { 
  
        switch(item.getItemId()) { 
        case R.id.opcion1: 
              return true; 	  
         case R.id.opcion3: 
              return true; 	 
        
         case R.id.opcion2: 
              return true;  
        
        } 
        return super.onMenuItemSelected(featureId, item); 
  }

Ahora sobreescribimos el método onMenuItemSelected para verificar cual de los MenuItem (<item>) que hemos creado ha sido pulsado; el parámetro Menuitem tiene referencia al elemento que ha sido pulsado por el usuario; obteniendo el id llamando al método getItemId; a través del switch verificamos cual recurso ha seleccionado el usuario para realizar una tarea en específico.

El enfoque "Legacy" que vimos es útil para mantener proyectos antiguos, pero para nuevos desarrollos, Jetpack Compose es el camino a seguir. Podrás encontrar el ejemplo completo del menú legacy en nuestro repositorio de GitHub Android/menu-opciones o haciendo clic aquí.

El siguiente paso, aprende a usar los ProgressBar en Android Studio.

Aprende a implementar Dropdown Menus en Jetpack Compose. Domina el uso de estados reactivos, iconos y la gestión de acciones como editar y eliminar en tus apps.

Acepto recibir anuncios de interes sobre este Blog.

Andrés Cruz

EN In english