web-dev-qa-db-fra.com

PopupMenu avec des icônes

Bien sûr, nous traitons ici avec le SDK 11 et supérieur.

J'ai l'intention de faire quelque chose de semblable à ceci: enter image description here

A côté de chaque item dans ce PopupMenu, je voudrais placer un icône.

J'ai créé un fichier XML et je l'ai placé dans /menu:

<menu xmlns:Android="http://schemas.Android.com/apk/res/Android" >

    <item
        Android:id="@+id/action_one"
        Android:title="Sync"
        Android:icon="@Android:drawable/ic_popup_sync"
        />

    <item
        Android:id="@+id/action_two"
        Android:title="About"
        Android:icon="@Android:drawable/ic_dialog_info"
        />
</menu>

Comme vous l'avez remarqué, dans le fichier xml, je définis les icônes que je souhaite. Toutefois, lorsque le menu contextuel s'affiche, elles sont affichées sans les icônes. Que dois-je faire pour que ces 2 icônes apparaissent?

46
Alex

Je le mettrais en œuvre autrement:

Créez une mise en page PopUpWindow:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:id="@+id/llSortChangePopup"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:background="@drawable/sort_popup_background"
Android:orientation="vertical" >

<TextView
    Android:id="@+id/tvDistance"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:text="@string/distance"
    Android:layout_weight="1.0"
    Android:layout_marginLeft="20dp"
    Android:paddingTop="5dp"
    Android:gravity="center_vertical"
    Android:textColor="@color/my_darker_gray" />

<ImageView
    Android:layout_marginLeft="11dp"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:src="@drawable/sort_popup_devider" 
    Android:contentDescription="@drawable/sort_popup_devider"/>

<TextView
    Android:id="@+id/tvPriority"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:text="@string/priority"
    Android:layout_weight="1.0"
    Android:layout_marginLeft="20dp"
    Android:gravity="center_vertical"
    Android:clickable="true"
    Android:onClick="popupSortOnClick"
    Android:textColor="@color/my_black" />


<ImageView
    Android:layout_marginLeft="11dp"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:src="@drawable/sort_popup_devider" 
    Android:contentDescription="@drawable/sort_popup_devider"/>

<TextView
    Android:id="@+id/tvTime"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:text="@string/time"
    Android:layout_weight="1.0"
    Android:layout_marginLeft="20dp"
    Android:gravity="center_vertical"
    Android:clickable="true"
    Android:onClick="popupSortOnClick"
    Android:textColor="@color/my_black" />

<ImageView
    Android:layout_marginLeft="11dp"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:src="@drawable/sort_popup_devider" 
    Android:contentDescription="@drawable/sort_popup_devider"/>

<TextView
    Android:id="@+id/tvStatus"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:text="@string/status"
    Android:layout_weight="1.0"
    Android:layout_marginLeft="20dp"
    Android:gravity="center_vertical"
    Android:textColor="@color/my_black" 
    Android:clickable="true"
    Android:onClick="popupSortOnClick"
    Android:paddingBottom="10dp"/>

 </LinearLayout>

puis créez le PopUpWindow dans votre Activity:

    // The method that displays the popup.
private void showStatusPopup(final Activity context, Point p) {

   // Inflate the popup_layout.xml
   LinearLayout viewGroup = (LinearLayout) context.findViewById(R.id.llStatusChangePopup);
   LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   View layout = layoutInflater.inflate(R.layout.status_popup_layout, null);

   // Creating the PopupWindow
   changeStatusPopUp = new PopupWindow(context);
   changeStatusPopUp.setContentView(layout);
   changeStatusPopUp.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);
   changeStatusPopUp.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
   changeStatusPopUp.setFocusable(true);

   // Some offset to align the popup a bit to the left, and a bit down, relative to button's position.
   int OFFSET_X = -20;
   int OFFSET_Y = 50;

   //Clear the default translucent background
   changeStatusPopUp.setBackgroundDrawable(new BitmapDrawable());

   // Displaying the popup at the specified location, + offsets.
   changeStatusPopUp.showAtLocation(layout, Gravity.NO_GRAVITY, p.x + OFFSET_X, p.y + OFFSET_Y);
}

enfin, faites-le apparaître onClick d'un bouton ou de quelque chose d'autre:

 imTaskStatusButton.setOnClickListener(new OnClickListener() 
        {
            public void onClick(View v) 
            {
                 int[] location = new int[2];
                 currentRowId = position;
                 currentRow = v;    
                 // Get the x, y location and store it in the location[] array
                 // location[0] = x, location[1] = y.
                 v.getLocationOnScreen(location);

                 //Initialize the Point with x, and y positions
                 point = new Point();
                 point.x = location[0];
                 point.y = location[1];
                 showStatusPopup(TasksListActivity.this, point);
            }
        });

Bon exemple pour PopUpWindow:

http://androidresearch.wordpress.com/2012/05/06/how-to-create-popups-in-Android/

36
Emil Adz

Cette méthode fonctionne si vous utilisez AppCompat v7. C'est un peu hacky mais de manière significative mieux que d'utiliser la réflexion et vous permet toujours d'utiliser le noyau Android PopupMenu:

PopupMenu menu = new PopupMenu(getContext(), overflowImageView);
menu.inflate(R.menu.popup);
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { ... });

MenuPopupHelper menuHelper = new MenuPopupHelper(getContext(), (MenuBuilder) menu.getMenu(), overflowImageView);
menuHelper.setForceShowIcon(true);
menuHelper.show();

res/menu/popup.xml

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

    <item Android:id="@+id/menu_share_location"
        Android:title="@string/share_location"
        Android:icon="@drawable/ic_share_black_24dp"/>

</menu>


Le menu contextuel utilise l’icône définie dans le fichier de ressources de votre menu:

enter image description here

79
Stephen Kidson

Le menu contextuel Android a une méthode cachée pour afficher l’icône du menu. Utilisez Java réflexion pour l'activer comme extrait de code ci-dessous.

public static void setForceShowIcon(PopupMenu popupMenu) {
    try {
        Field[] fields = popupMenu.getClass().getDeclaredFields();
        for (Field field : fields) {
            if ("mPopup".equals(field.getName())) {
                field.setAccessible(true);
                Object menuPopupHelper = field.get(popupMenu);
                Class<?> classPopupHelper = Class.forName(menuPopupHelper
                        .getClass().getName());
                Method setForceIcons = classPopupHelper.getMethod(
                        "setForceShowIcon", boolean.class);
                setForceIcons.invoke(menuPopupHelper, true);
                break;
            }
        }
    } catch (Throwable e) {
        e.printStackTrace();
    }
}
21
Bao Le

Menu contextuel avec une icône utilisant MenuBuilder et MenuPopupHelper

    MenuBuilder menuBuilder =new MenuBuilder(this);
    MenuInflater inflater = new MenuInflater(this);
    inflater.inflate(R.menu.menu, menuBuilder);
    MenuPopupHelper optionsMenu = new MenuPopupHelper(this, menuBuilder, view);
    optionsMenu.setForceShowIcon(true);

    // Set Item Click Listener
    menuBuilder.setCallback(new MenuBuilder.Callback() {
        @Override
        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
            switch (item.getItemId()) {
                case R.id.opt1: // Handle option1 Click
                    return true;
                case R.id.opt2: // Handle option2 Click
                    return true;
                default:
                    return false;
            }
        }

        @Override
        public void onMenuModeChange(MenuBuilder menu) {}
    });


    // Display the menu
    optionsMenu.show();

menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <item
        Android:id="@+id/opt1"
        Android:icon="@mipmap/ic_launcher"
        Android:title="option 1" />
    <item
        Android:id="@+id/opt2"
        Android:icon="@mipmap/ic_launcher"
        Android:title="option 2" />
</menu>

enter image description here

18
Ajay Sivan

La classe MenuPopupHelper dans AppCompat a le @hide annotation. Si cela vous pose problème, ou si vous ne pouvez pas utiliser AppCompat pour une raison quelconque, il existe une autre solution utilisant un Spannable dans le titre MenuItem qui contient à la fois l’icône et le texte du titre.

Les principales étapes sont les suivantes:

  • gonflez votre PopupMenu avec un fichier menu xml
  • si l'un des éléments possède une icône, faites ceci pour tous des éléments:
    • si l'élément n'a pas d'icône, créez une icône transparente. Cela garantit que les éléments sans icônes seront alignés avec les éléments avec des icônes
    • créer un SpannableStringBuilder contenant l'icône et le titre
    • définir le titre de l'élément de menu sur SpannableStringBuilder
    • définir l'icône de l'élément de menu sur null, "juste au cas où"

Avantages: pas de réflexion. N'utilise pas d'apis cachés. Peut travailler avec le framework PopupMenu.

Inconvénients: Plus de code. Si vous avez un sous-menu sans icône, il y aura un remplissage gauche non désiré sur un petit écran.


Détails:

Tout d’abord, définissez une taille pour l’icône dans un fichier dimens.xml fichier:

<dimen name="menu_item_icon_size">24dp</dimen>

Ensuite, quelques méthodes pour déplacer les icônes définies dans XML dans les titres:

/**
 * Moves icons from the PopupMenu's MenuItems' icon fields into the menu title as a Spannable with the icon and title text.
 */
public static void insertMenuItemIcons(Context context, PopupMenu popupMenu) {
    Menu menu = popupMenu.getMenu();
    if (hasIcon(menu)) {
        for (int i = 0; i < menu.size(); i++) {
            insertMenuItemIcon(context, menu.getItem(i));
        }
    }
}

/**
 * @return true if the menu has at least one MenuItem with an icon.
 */
private static boolean hasIcon(Menu menu) {
    for (int i = 0; i < menu.size(); i++) {
        if (menu.getItem(i).getIcon() != null) return true;
    }
    return false;
}

/**
 * Converts the given MenuItem's title into a Spannable containing both its icon and title.
 */
private static void insertMenuItemIcon(Context context, MenuItem menuItem) {
    Drawable icon = menuItem.getIcon();

    // If there's no icon, we insert a transparent one to keep the title aligned with the items
    // which do have icons.
    if (icon == null) icon = new ColorDrawable(Color.TRANSPARENT);

    int iconSize = context.getResources().getDimensionPixelSize(R.dimen.menu_item_icon_size);
    icon.setBounds(0, 0, iconSize, iconSize);
    ImageSpan imageSpan = new ImageSpan(icon);

    // Add a space placeholder for the icon, before the title.
    SpannableStringBuilder ssb = new SpannableStringBuilder("       " + menuItem.getTitle());

    // Replace the space placeholder with the icon.
    ssb.setSpan(imageSpan, 1, 2, 0);
    menuItem.setTitle(ssb);
    // Set the icon to null just in case, on some weird devices, they've customized Android to display
    // the icon in the menu... we don't want two icons to appear.
    menuItem.setIcon(null);
}

Enfin, créez votre PopupMenu et utilisez les méthodes ci-dessus avant de l’afficher:

PopupMenu popupMenu = new PopupMenu(view.getContext(), view);
popupMenu.inflate(R.menu.popup_menu);
insertMenuItemIcons(textView.getContext(), popupMenu);
popupMenu.show();

Capture d'écran: screenshot

15
Carmen

Vous pouvez implémenter ceci en utilisant Reflection si vous ne le connaissez pas à l’aide de cet impressionnant Java vous pouvez modifier le comportement d’exécution des applications exécutées dans la machine virtuelle Java). au niveau de l'objet et exécute ses méthodes au moment de l'exécution. Dans notre cas, nous devons modifier le comportement de popupMenu au moment de l'exécution au lieu d'étendre la classe principale et de la modifier;) espérons que l'aide

private void showPopupMenu(View view) {
    // inflate menu
    PopupMenu popup = new PopupMenu(mcontext, view);
    MenuInflater inflater = popup.getMenuInflater();
    inflater.inflate(R.menu.main, popup.getMenu());

    Object menuHelper;
    Class[] argTypes;
    try {
        Field fMenuHelper = PopupMenu.class.getDeclaredField("mPopup");
        fMenuHelper.setAccessible(true);
        menuHelper = fMenuHelper.get(popup);
        argTypes = new Class[]{boolean.class};
        menuHelper.getClass().getDeclaredMethod("setForceShowIcon", argTypes).invoke(menuHelper, true);
    } catch (Exception e) {

    }
    popup.show();




} 
9
Mohamed Ayed

list_item_menu.xml dans le répertoire/res/menu

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

            <item
                Android:id="@+id/locale"
                Android:title="Localizar"
                Android:icon="@mipmap/ic_en_farmacia_ico"
                app:showAsAction="always">
            </item>

            <item Android:id="@+id/delete"
                Android:title="Eliminar"
                Android:icon="@mipmap/ic_eliminar_ico"
                app:showAsAction="always">
            </item>
    </menu>

Dans mon activité

private void showPopupOption(View v){
    PopupMenu popup = new PopupMenu(getContext(), v);
    popup.getMenuInflater().inflate(R.menu.list_item_menu, popup.getMenu());

    popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
        public boolean onMenuItemClick(MenuItem menu_item) {
            switch (menu_item.getItemId()) {
                case R.id.locale:
                    break;
                case R.id.delete:
                    break;
            }
            return true;
        }
    });

    MenuPopupHelper menuHelper = new MenuPopupHelper(getContext(), (MenuBuilder) popup.getMenu(), v);
    menuHelper.setForceShowIcon(true);
    menuHelper.setGravity(Gravity.END);
    menuHelper.show();
}

résultat

popup_menu

8

Lisez le code source PopupMenu. Nous pouvons montrer l'icône par le code ci-dessous:

Field field = popupMenu.getClass().getDeclaredField("mPopup");
field.setAccessible(true);
MenuPopupHelper menuPopupHelper = (MenuPopupHelper) field.get(popupMenu);
menuPopupHelper.setForceShowIcon(true);

Mais MenuPopupHelper.Java est dans le package interne Android). Nous devrions donc utiliser Reflection:

    PopupMenu popupMenu = new PopupMenu(this, anchor);
    popupMenu.getMenuInflater().inflate(R.menu.process, popupMenu.getMenu());

    try {
        Field field = popupMenu.getClass().getDeclaredField("mPopup");
        field.setAccessible(true);
        Object menuPopupHelper = field.get(popupMenu);
        Class<?> cls = Class.forName("com.Android.internal.view.menu.MenuPopupHelper");
        Method method = cls.getDeclaredMethod("setForceShowIcon", new Class[]{boolean.class});
        method.setAccessible(true);
        method.invoke(menuPopupHelper, new Object[]{true});
    } catch (Exception e) {
        e.printStackTrace();
    }

    popupMenu.show();
8
yinghuihong

J'ai résolu mon problème de la manière la plus simple possible, sans jamais m'attendre à une telle simplicité:

Dans main.xml:

<menu xmlns:Android="http://schemas.Android.com/apk/res/Android" >

<item
    Android:id="@+id/action_more"
    Android:icon="@Android:drawable/ic_menu_more"
    Android:orderInCategory="1"
    Android:showAsAction="always"
    Android:title="More">
    <menu>
        <item
            Android:id="@+id/action_one"
            Android:icon="@Android:drawable/ic_popup_sync"
            Android:title="Sync"/>
        <item
            Android:id="@+id/action_two"
            Android:icon="@Android:drawable/ic_dialog_info"
            Android:title="About"/>
    </menu>
</item>

dans MainActivity.Java

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

C'était un truc en utilisant un sous-men

5
Alex

Si vous voulez afficher l'icône dans le menu contextuel, regardez https://github.com/shehabic/Droppy , c'est plutôt cool et facile à utiliser

4
Gintama

Basé sur @Ajay answer ... voici ce que j'ai fait

 @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.add_task, menu);  // for the two icons in action bar
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {

            case R.id.menu:
                View menuItemView = findViewById(R.id.menu);
                MenuBuilder menuBuilder =new MenuBuilder(this);
                MenuInflater inflater = new MenuInflater(this);
                inflater.inflate(R.menu.popup, menuBuilder);
                MenuPopupHelper optionsMenu = new MenuPopupHelper(this, menuBuilder, menuItemView); 
                optionsMenu.setForceShowIcon(true);
                optionsMenu.show();

            default:
                return super.onOptionsItemSelected(item);
        }
    }

popup

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:Android="http://schemas.Android.com/apk/res/Android">
<item
    Android:id="@+id/opt1"
    Android:icon="@drawable/change_pic"
    Android:title="Change Picture" />
<item
    Android:id="@+id/opt2"
    Android:icon="@drawable/change_pin"
    Android:title="Change Password" />

    <item
        Android:id="@+id/opt3"
        Android:icon="@drawable/sign_out"
        Android:title="Sign Out" />
</menu>

ScreenShot

enter image description here

1
John Joe

J'essayais la réponse de @Stephen Kidson et la suggestion de @ david.schereiber, et je me suis rendu compte qu'il n'y a pas de telle méthode setOnMenuItemClickListener dans MenuBuilder. J'ai déconné avec le code source de la v7 et j'ai trouvé cette solution:

        MenuBuilder menuBuilder = new MenuBuilder(mContext);
        new SupportMenuInflater(mContext).inflate(R.menu.my_menu, menuBuilder);
        menuBuilder.setCallback(new MenuBuilder.Callback() {
            @Override
            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem menuItem) {
                // your "setOnMenuItemClickListener" code goes here
                switch (menuItem.getItemId()) {
                    case R.id.menu_id1:
                        // do something 1
                        return true;

                    case R.id.menu_id2:
                        // do something 2
                        return true;
                }
                return false;
            }

            @Override
            public void onMenuModeChange(MenuBuilder menu) {
            }
        });
        MenuPopupHelper menuHelper = new MenuPopupHelper(mContext, menuBuilder, v);
        menuHelper.setForceShowIcon(true); // show icons!!!!!!!!
        menuHelper.show();
0
Leo