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.
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.
- 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).
- 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.
- 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.
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:
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:
- Crear tres 9-patch para el fondo del menú correspondientes con los estados «normal», «con foco» y «presionado». Como ejemplo he usado los siguientes:
- 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>
- 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>
- 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>
- 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:
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.
como puede cambiar el color del texto? he probado de todo y no logro que funcione
Acabo de añadirlo al artículo
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
Buena pregunta, he intentado hacerlo pero no lo he conseguido.
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!
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