El PopupMenu
en Android es un menú que se ancla a un View para que aparezca por debajo de este en caso de existir el espacio para ello. De lo contrario se mostrará por encima.
A diferencia del menú de opciones y el contextual, la aparición del PopupMenu
es controlada por nosotros, basado en algún evento de interés sobre un view.
Ejemplo de PopupMenu En Android
En este tutorial aprenderás a crear, mostrar y procesar los eventos de un PopupMenu
a partir del siguiente ejemplo de ilustración:
Como ves, se trata de una interacción simple donde haces click a un ImageButton
y se despliega el menú con varias acciones relacionadas al contenido. Cuando seleccionas un ítem o cierras el menú, se despliega un mensaje indicando el evento.
Puedes descargar el proyecto Android Studio desde el siguiente enlace:
1. Definir View Ancla Del PopupMenu
Antes de nada, debes seleccionar al view que disparará la aparición del menú con las acciones relacionadas al contenido específico.
Pongamos por caso nuestra App de ejemplo, donde el view que sirve de ancla es un ImageView
. El archivo res/activity_main.xml
contiene el diseño donde se encuentra este elemento:
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/blue_50"
android:padding="16dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/post_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/post_title"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintEnd_toStartOf="@+id/more_actions_button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toEndOf="@+id/post_featured_image"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/post_intro"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/post_description"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/more_actions_button"
app:layout_constraintStart_toEndOf="@+id/post_featured_image"
app:layout_constraintTop_toBottomOf="@+id/post_title" />
<ImageView
android:id="@+id/post_featured_image"
android:layout_width="44dp"
android:layout_height="44dp"
android:contentDescription="@string/post_image_desc"
android:src="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/more_actions_button"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/more_actions_desc"
android:padding="0dp"
android:src="@drawable/ic_more_actions"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Ahora bien, como el evento de click es el que muestra al menú, entonces añadimos una función lambda con setOnClickListener()
para dejar expresado la creación del mismo:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val moreButton: ImageView = findViewById(R.id.more_actions_button)
moreButton.setOnClickListener { view ->
showMoreActionsMenu(view)
}
}
private fun showMoreActionsMenu(button: View) {
}
}
Al interior de showMoreActionMenu()
añadiremos la lógica de creación que veremos en los siguientes pasos.
2. Crear Recurso De Menú
Acto seguido, crea el recurso de menú que contendrá la definición XML de las acciones a presentar ante el usuario. Recuerda que en Android Studio esto se realiza con New > Android Resource File.
Debido a que tendremos tres acciones, entonces añadimos una etiqueta <item>
por cada una y asignamos los títulos correspondientes:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_favorites"
android:title="@string/add_favorites" />
<item
android:id="@+id/share"
android:title="@string/share" />
<item
android:id="@+id/move"
android:title="@string/move" />
</menu>
3. Crear PopupMenu En Kotlin
Luego usa el constructor público de la clase PopupMenu
para crear la instancia que será mostrada e infla el recurso de menú sobre ella con el método MenuInflater.inflate()
:
private fun showMoreActionsMenu(button: View) {
val popupMenu = PopupMenu(this, button)
menuInflater.inflate(R.menu.main_menu, popupMenu.menu)
popupMenu.show()
}
Pasa a la propiedad PopupMenu.menu
al inflado para convertir el recurso en una implementación de Menu
, con el fin de que el menú modal haga uso de ella.
Al final del método, llama a show()
desde para mostrarlo en pantalla. Verás que su aparición será por debajo del botón de overflow.
Crear PopupMenu Programáticamente
Por otro lado, si quisieras producir el mismo resultado anterior pero en tiempo de ejecución, entonces crea la instancia PopupMenu
y luego añade los ítems con el método add()
:
private fun showMoreActionsMenu(button: View) {
val popupMenu = PopupMenu(this, button)
popupMenu.menu.add(R.string.add_favorites)
popupMenu.menu.add(R.string.share)
popupMenu.menu.add(R.string.move)
popupMenu.show()
}
Recuerda que el objeto mostrado en pantalla se encuentra en la propiedad menu
, por lo que es sobre ella que realizas la adición o cualquier otra operación de modificación.
4. Manejar Eventos De Click
Habíamos dicho al inicio que cuando se haga click sobre los ítems del menú mostraríamos un mensaje que evidencia la captura del evento:
¿Cómo logras este manejo?
A través de la escucha PopupMenu.OnMenuItemClickListener
y su controlador onMenuItemClick()
. Este método recibe la instancia MenuItem
del elemento clickeado y retorna un booleano (true
para representar evento consumido, false
para lo contrario).
Asimismo debes asignar al callback al menú con el método setOnMenuItemClickListener()
, la cual puede ser creada anónimamente, una referencia o implementarla sobre la actividad o fragmento donde vive el menú:
private fun showMoreActionsMenu(button: View) {
val popupMenu = PopupMenu(this, button)
menuInflater.inflate(R.menu.main_menu, popupMenu.menu)
popupMenu.setOnMenuItemClickListener (::manageItemClick)
popupMenu.show()
}
private fun manageItemClick(menuItem: MenuItem): Boolean {
return when(menuItem.itemId){
R.id.add_favorites, R.id.share, R.id.move-> {
showMessage(menuItem.title)
true
}
else -> false
}
}
private fun showMessage(title: CharSequence) {
Toast.makeText(this, title, Toast.LENGTH_SHORT).show()
}
El ejemplo anterior pasa la referencia del método manageItemClick()
a setOnMenuItemClickListener()
para manejar los clicks.
Su objetivo es exactamente el que hemos visto en todos los menús hasta ahora. Usar la expresión when
para dirigir el flujo dependiendo del ID del ítem.
Debido a que estamos viendo un ejemplo simple, todos los ítems muestran un Toast
con showMessage()
a partir de su propiedad title
.
5. Manejar Evento De Descarte
Y finalmente, veamos que también existe un observador para cuando el menú se cierra debido a un descarte o consumo de click en alguno de sus ítems.
Su nombre es PopupMenu.OnDismissListener
y tiene un solo controlador llamado onDismiss()
, el cual recibe la instancia del menú modal:
private fun showMoreActionsMenu(button: View) {
// ...
popupMenu.setOnDismissListener(::manageDismiss)
// ...
}
private fun manageDismiss(popupMenu: PopupMenu) {
showMessage("Menú cerrado")
}
En el código anterior, usamos setOnDismissListener()
para mostrar un Toast
en el momento que se desvanece el menú. Al mismo tiempo usamos la referencia de manageDismiss()
para entregarle el control del cierre.
¿Qué Sigue?
En este tutorial aprendiste a crear, inflar, mostrar y manejar eventos de un PopupMenu
en Android. Aquí termina la sección de menús en Android. Ahora puedes avanzar al apartado de ajustes (todo) de usuario para aprender a crear la interfaz con la librería de preferencias.
Más Tutoriales Android
- Recursos de menú
- Options Menu
- ContextMenu
- Tutoriales de UI en Android
- Todos los tutoriales Android
- Cursos 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!