Menú Contextual Flotante En Android

El menú contextual flotante en Android es un menú que muestra una lista de opciones asociadas al contexto de un view, al cual el usuario le hizo un click prolongado:

Apariencia de ContextMenu en Android

¿Cómo se comporta?

Las acciones mostradas en el menú se ajustan al contexto del ítem que está involucrado, por lo que solo es posible seleccionar una de ellas para aplicar. Adicional puedes especificar un título para su cabecera («Food Space.jpg» en la imagen anterior).

¿Qué elementos del framework uso?

El menú contextual flotante es representado por la clase ContextMenu en el framework de Android. Conviene subrayar que este componente necesitará de:

  • Un recurso de menú
  • A ContexMenu.ContextMenuInfo para proveeer información al crear el menú
  • A MenuInflater.inflate() para inflar el menú
  • A registerForContextMenu() para registrar el view
  • A onCreateContextMenu() para crear el menú
  • A onContextItemSelected() para responder a los eventos de las opciones

No te preocupes, la idea de los siguientes apartados es explicar cómo encadenar los anteriores elementos.

Ejemplo De Menú Contextual Flotante En Android

En este tutorial aprenderás a asociar el disparo de una acción contextual a un view, crear y mostrar el menú que cubre dicha acción; y procesar los eventos sobre cada opción.

Asimismo crearás una App de ejemplo que despliega un menú contextual flotante cuando se hace click y se sostiene un ImageView:

App ejemplo de menú contextual flotante en Android
La imagen es proveída desde Freepik

Puedes descargar el proyecto Android Studio desde el siguiente enlace por si necesitas corroborar el código en más detalle:

Con esto claro, adentrémonos a la receta para crear el menú.

1. Crear Recurso De Menú

En primer lugar, crea el archivo XML con la definición de la estructura del menú que deseas mostrar.

Menú contextual flotante en Android Studio
Menú contextual flotante en Android Studio

Por tanto añade tres elementos para recrear las acciones de: Descargar, Compatir y Copiar descripción; sobre el menú flotante que usamos en la App de ejemplo:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/download"
        android:title="@string/download" />
    <item
        android:id="@+id/share"
        android:title="@string/share" />
    <item
        android:id="@+id/copy"
        android:title="@string/copy_description" />
</menu>

Ten en cuenta que los ítems de menú de la clase ContextMenu no soportan atajos ni iconos, por lo que los valores que asignes en atributos como android:icon y android:numericShorcut perderán efecto.

2. Asociar View Con ContextMenu

Luego debes registrar el view que actuará como origen de la aparición del menú contextual flotante. Dicho registro lo logras con el método registerForContextMenu() desde tu actividad pasando la instancia del view:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val image: ImageView = findViewById(R.id.image)
        registerForContextMenu(image)
    }   
}

En concreto pasamos al ImageView que tenemos en el layout de la actividad a partir de una plantilla Empty Activity en Android Studio:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:padding="16dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:id="@+id/image"
        android:src="@drawable/image"
        android:scaleType="centerCrop"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:contentDescription="@string/image_content_description" />

</androidx.constraintlayout.widget.ConstraintLayout>

3. Crear ContextMenu

Al igual que con el menú de opciones, las actividades y fragmentos poseen un método de enganche para inflar el menú contextual flotante llamado onCreateContextMenu():

override fun onCreateContextMenu(
    menu: ContextMenu,
    v: View,
    menuInfo: ContextMenu.ContextMenuInfo?
) {
    super.onCreateContextMenu(menu, v, menuInfo)

    menuInflater.inflate(R.menu.context_menu, menu)
    menu.setHeaderTitle(R.string.image_content_description)
}

Este método es llamado por el sistema cuando se notifica un click prolongado sobre el view registrado. Y como ves, recibe los siguientes tres parámetros:

  1. menu: El MenuContext que está siendo construido
  2. v: El view para el cual se está construyendo el menú flotante
  3. menuInfo: Información extra acerca del view relacionado al menú

Debido a que tienes un recurso de menú construido, simplemente invocas a inflate() pasando a menu para inflar la definición sobre el objeto.

Crear ContextMenu en Android
Corre la App y haz click prolongado sobre la imagen para desplegar el ContextMenu

En cuanto al título de la cabecera, invoca a setHeaderTitle() desde el menú y pasa el recurso string o CharSequence que será mostrado.

Añadir Items Programáticamente

Por otra parte, si lo que quieres es crear el menú programáticamente, entonces usa el método add() para agregar ítems a la instancia en construcción del menú:

override fun onCreateContextMenu(
    menu: ContextMenu,
    v: View,
    menuInfo: ContextMenu.ContextMenuInfo?
) {
    super.onCreateContextMenu(menu, v, menuInfo)
    menu.setHeaderTitle(R.string.image_content_description)
    menu.add(R.string.download)
    menu.add(R.string.share)
    menu.add(R.string.copy_description)
}

El anterior código producirá el mismo resultado visual que nuestro recurso XML.

4. Responder A Selección De Ítems

El siguiente paso es sobrescribir al método onContextItemSelected() con el fin de procesar la selección de ítems del menú contextual flotante.

Escuchar clicks de menú con onContextItemSelected()

Su funcionamiento es similar a onOptionsItemSelected(), recibe como argumento la instancia MenuItem seleccionada por el usuario:

override fun onContextItemSelected(item: MenuItem): Boolean {
    Toast.makeText(this, item.title, Toast.LENGTH_SHORT).show()

    return when (item.itemId) {
        R.id.download -> {
            download()
            true
        }
        R.id.share -> {
            share()
            true
        }
        R.id.copy -> {
            copy()
            true
        }
        else -> super.onContextItemSelected(item)
    }
}

Puesto que tenemos varios ítems del menú a procesar usamos la expresión when para realizar las comparaciones de los IDs existentes con el estado de itemId del ítem clickeado.

Por consiguiente, cada caso manejado correctamente retornará true, de lo contrario retornamos la implementación del padre.

Añadir Escucha Programáticamente

También puedes omitir el uso de onOptionsItemSelected() si añades observadores del tipo OnMenuItemListener sobre las instancias MenuItem producidas por add():

override fun onCreateContextMenu(
    menu: ContextMenu,
    v: View,
    menuInfo: ContextMenu.ContextMenuInfo?
) {
    super.onCreateContextMenu(menu, v, menuInfo)

    menuInflater.inflate(R.menu.context_menu, menu)
    menu.setHeaderTitle(R.string.image_content_description)
    menu.add("Cambiar nombre").setOnMenuItemClickListener { item ->
        showMessage(item.title)
        true
    }
}

La escucha tiene un solo controlador (onMenuItemClick()), por lo que es posible convertir la clase anónima en una lambda.

Procesar click con OnMenuItemClickListener
Procesar click con OnMenuItemClickListener

5. Responder A Cierre Del Menú

Para terminar, procesaremos el cierre del menú contextual flotante con la sobrescritura del método onContextMenuClosed() de la actividad. Concretamente mostraremos un Toast evidenciando la ocurrencia de este evento:

Responder a evento de cierre de menú contextual flotante

Como resultado tendremos una sencilla línea de creación al interior de onContextMenuClosed():

override fun onContextMenuClosed(menu: Menu) {
    showMessage("Menú cerrado")
}

¿Qué Sigue?

En este tutorial aprendiste a crear menús contextuales flotantes para permitir al usuario ejecutar acciones relacionadas con un view específico. Ahora puede ir a estudiar el menú contextual producido por el Action Mode cuando se interactúa con una lista de ítems.

Más Contenidos Android

Ú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!