Diseño Android: Popup Menu

Última actualización: 17/05/2014

android

El primer artículo de la serie «Diseño Android» de 2014 está dedicado al widget PopupMenu que permite mostrar un menú contextual en un popup ubicado junto al elemento que lo despliega. Este widget fue introducido en HoneyComb (API 11) pero puede ser utilizado en versiones anteriores ya que se encuentra incluido en la libreria de compatibilidad v7.

La siguiente captura de la app oficial de Youtube muestra un popup menu como el que se va a utilizar en el presente artículo.

youtube-popup menu

Proyecto de ejemplo

Se va a utilizar como base para el artículo un proyecto estándar compatible con Android 2.1 (API 7) por lo que hay que incluir como dependencia la libreria de compatibilidad v7 (al final del artículo se indicarán los cambios a realizar para utilizar un Popup Mneu de forma nativa si la app no es compatible con Android 2).

En los artículos sobre ViewPager se expuso cómo incluir la liberia v4 pero esta es un poco distinta ya que no consiste en un simple .jar sino que además está compuesta de recursos como drawable y layouts, así que en primer lugar vamos a ver paso a paso cómo importarla en Eclipse para poder utilizarla en nuestros proyectos.

  1. Comprobar que ya está descargada la última versión y si no descargarla. Esto se hace desde el SDK Manager en la sección de extras (si está instalada poniendo el puntero sobre la misma se mostrará la ubicación).
    android sdk manager
  2. Desde Eclipse, nos vamos a File->Import->Existing Projects into Workspace. El proyecto a importar se llama android-support-v7-appcompat y se encuentra en /extras/android/support/v7/appcompat.
    eclipse import
  3. Para incluir este proyecto como dependencia de cualquier proyecto Android, abrimos las propiedades del proyecto y nos vamos a la sección Android. Desde ahí, en «Library» se añade el proyecto.

    Android Library

Para más información sobre la importación de librerías en Eclipse ADT y Android Studio, consultar este artículo.

Ahora vamos con el proyecto de ejemplo propiamente dicho. Se va a utilizar el tema Holo Light, en nuestro caso el proporcionado por la libreria de compatibilidad ya que este estilo pertenece de forma nativa a Android 4.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.danielme.blog.android.popupmenu"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="7"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" 
        android:theme="@style/Theme.AppCompat.Light">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" 
            android:configChanges="keyboardHidden|orientation|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity> 
        
    </application>

</manifest>

La única Activity está asociada al siguiente layout con dos botones que deberán mostrar un popup distinto. Como imagen del botón, en nuestro caso un ImageView, se utilizará el icono «abc_ic_menu_moreoverflow_normal_holo_light» utilizado por defecto en la ActionBar. Asimismo, para que el botón sólo muestre la imagen con el fondo trasparente se aplicará el estilo «selectableItemBackground».

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" 
    android:paddingLeft="10dp">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/menu1"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <ImageButton
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/abc_ic_menu_moreoverflow_normal_holo_light" 
            android:onClick="menuOne"
            android:background="?attr/selectableItemBackground"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal" >

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/menu2"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <ImageButton
            android:id="@+id/imageView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/abc_ic_menu_moreoverflow_normal_holo_light" 
            android:onClick="menuTwo"
            android:background="?attr/selectableItemBackground"/>
    </LinearLayout>

</LinearLayout>

Implementación del menú

El menú se define como cualquier otro menú mediante xml en el directorio /res/menu.xml

menu1.xml

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

    <item
        android:id="@+id/option1"
        android:title="@string/option1"/>
    <item
        android:id="@+id/option2"
        android:title="@string/option2"/>
    <item
        android:id="@+id/option3"
        android:title="@string/option3"/>

</menu>

menu2.xml

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

    <item
        android:id="@+id/option4"
        android:title="@string/option4"/>
    <item
        android:id="@+id/option5"
        android:title="@string/option5"/>

</menu>

Lo más interesante está en la Activity y hay muy poco que contar ya que lo único que hay que hacer es instanciar el menú cuando se pulse el botón correspondiente, implementando el listener con la lógica a ejecutar en el caso de que el usuario pulse alguna entrada del mismo (pulsando back o cualquier parte de la pantalla fuera del menú este simplemente se cierra). Asimismo, se especializa ActionBarActivity en lugar de Activity para que en Android se muestre la ActionBar.

package com.danielme.blog.android.popupmenu;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.PopupMenu.OnMenuItemClickListener;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

/**
 * 
 * @author danielme.com
 * 
 */
public class MainActivity  extends ActionBarActivity
{

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}

	public void menuOne(View view)
	{
		showPopupMenu(view, R.menu.menu1);
	}

	public void menuTwo(View view)
	{
		showPopupMenu(view, R.menu.menu2);
	}

	private void showPopupMenu(View view, int menu)
	{
		PopupMenu popupMenu = new PopupMenu(this, view);
		popupMenu.getMenuInflater().inflate(menu, popupMenu.getMenu());	
		popupMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {

			@Override
			public boolean onMenuItemClick(MenuItem item)
			{
				Toast.makeText(MainActivity.this, item.getTitle(), Toast.LENGTH_SHORT).show();
				return true;
			}
		});
		//dont forget to show the menu
		popupMenu.show();
	}

}

El resultado en Gingerbread:

android popup menu

Aplicando estilos

Ahora que ya tenemos el popup menu en nuestra app, vamos a ver cómo cambiar su estilo que por defecto es heredado del estilo de Android que utilicemos como base. Para ello, hay que sobreescribir o redefinir dos estilos propios de la ActionBar: “android:popupMenuStyle” y “android:dropDownListViewStyle”. La manera de hacerlo, o al menos la que a mi me ha funcionado, es la siguiente:

  1. Crear tres 9-patch para el fondo del menú correspondientes con los estados «normal», «con foco» y «presionado». Como ejemplo he usado los siguientes:
    menu.9

    menu_focused.9

    menu_pressed.9

  2. Se crea un tema llamado, por ejemplo, «dm» que herede de «Theme.AppCompat.Light» y modifique los estilos que necesitamos
    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:android="http://schemas.android.com/apk/res/android">
    
        <style name="dm" parent="@style/Theme.AppCompat.Light">
            <item name="popupMenuStyle">@style/menu</item>
            <item name="dropDownListViewStyle">@style/dropDownListView.dm</item>
            <item name="android:itemTextAppearance">@style/itemTextStyle.dm</item>
        </style>
    
        <!-- default background -->
        <style name="menu" parent="android:Widget.ListPopupWindow">
            <item name="android:popupBackground">@drawable/menu</item>
        </style>
    
        <!-- selector for pressed/focused states -->
        <style name="dropDownListView.dm" parent="@android:style/Widget.Holo.Light.ListView.DropDown">
            <item name="android:listSelector">@drawable/menu_selector</item>
        </style>
    
        <!-- font color -->
        <style name="itemTextStyle.dm" parent="@android:style/TextAppearance.Widget.IconMenu.Item">
            <item name="android:textColor">@drawable/menu_item_color</item>
        </style>
    
    </resources>
    
  3. El 9-patch como fondo por defecto se aplica directamente desde el styles.xml, pero para los otros dos estados hay que utilizar un selector, por ejemplo el siguiente:
    <?xml version="1.0" encoding="utf-8"?>
    
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="false" android:state_focused="true" android:drawable="@drawable/menu_focused" />
        <item android:state_pressed="true" android:drawable="@drawable/menu_pressed" />
        <item android:drawable="@android:color/transparent" />
    </selector>
    
  4. Para el color del texto, utilizamos el siguiente drawable. El color será negro y, cuando se pulse, blanco.
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item android:state_pressed="true" android:color="@android:color/white"/>
        <item android:state_focused="true" android:color="@android:color/white"/>
        <item android:color="@android:color/black"/>
    
    </selector>
    
  5. No nos olvidemos de aplicar este nuevo tema en el manifest:
     <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name" 
            android:theme="@style/dm">
    

Este es el resultado final:

android popup menu

Implementación nativa en Android 3+

Tal y como se comentó en la introducción, el widget Popup Menu forma parte de la API de Android desde la versión 3.0 al igual que ActionBar. Para adaptar el proyecto de ejemplo a una app para Android 3 y superior, además de no necesitar la librería de compatibilidad, son necesarios los siguientes cambios:

Support Library Android 3 +
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.PopupMenu; import android.widget.PopupMenu;
import android.support.v7.widget.PopupMenu.OnMenuItemClickListener; import android.widget.PopupMenu.OnMenuItemClickListener;
extends ActionBarActivity extends Activity
@style/Theme.AppCompat.Light android:Theme.Holo.Light
@android:style/Widget.Holo.Light.ListView.DropDown android:attr/selectableItemBackground
popupMenuStyle android:popupMenuStyle
dropDownListViewStyle android:dropDownListViewStyle
android:minSdkVersion=»7″ android:minSdkVersion=»11″
?attr/selectableItemBackground ?android:attr/selectableItemBackground

Además se han inluido en el proyecto los iconos abc_ic_menu_moreoverflow_normal_holo_light.

Código de ejemplo

El proyecto completo se encuentra disponible en GitHub en dos versiones:

Para más información sobre cómo utilizar GitHub, consultar este artículo.

6 comentarios sobre “Diseño Android: Popup Menu

  1. Una duda, se puede cambiar el color de fondo del menu que aparece arriba a la drecha, normalmente el menu de ajustes. Mi idea es que haya diferentes items, cada uno con un color.

    Muchas gracias de antemano

  2. hola, y si no tengo un boton para lanzarlo, es decir me gustaria simplemente poder lanzarlo y posicionarlo a mi gusto, se puede? como?

    muchas gracias, buen tuto!

  3. hola, y si no tengo un boton de donde lanzarlo, es decir yo tengo un webview en mi app y quiero lanzar el popUp y posicionarlo a mi gusto, se puede? saludos y buen tutorial!

    gracias

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.