Is it possible to display icons in a PopupMenu?
Asked Answered
E

16

61

I really like the new PopupMenu we got in 3.0, but I just can't display any icons next to the menu items in it. I'm inflating the menu from the .xml below:

<item android:id="@+id/menu_delete_product"
    android:icon="@drawable/sym_action_add"
    android:title="delete"
    android:showAsAction="ifRoom|withText" />

<item android:id="@+id/menu_modify_product"
    android:icon="@drawable/sym_action_add"
    android:title="modify"
    android:showAsAction="ifRoom|withText" />

<item android:id="@+id/menu_product_details"
    android:icon="@drawable/sym_action_add"
    android:title="details"
    android:showAsAction="ifRoom|withText" />

With this code:

image.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        PopupMenu pop = new PopupMenu(getActivity(), v);
        pop.getMenuInflater().inflate(R.menu.shelves_details_menu, pop.getMenu());
        pop.show();
    }
});

I can't get the icons to show up, am I missing something?

Erysipeloid answered 24/7, 2011 at 8:31 Comment(2)
It's worth noting the correct answer (2014) is simply: PopupMenu DOES NOT INCLUDE/SHOW ICONS. It's that simple. There are any number of workarounds, involving NOT using a PopupMenu, but using something else. OR a correct solution is extend PopupMenu, as @Madness explains below.Newsworthy
if everybody sticks to answers "that simple" stackoverflow would be very boring. let the creativity flow please.Weigel
M
36

If you're willing to be a bit adventurous, look at Google's source code for PopupMenu. Create your own class i.e. MyPopupMenu that is the same as Google's PopupMenu class, but make one slight change.

In PopupMenu's constructor:

public MyPopupMenu(Context context, View anchor) {
    // TODO Theme?
    mContext = context;
    mMenu = new MenuBuilder(context);
    mMenu.setCallback(this);
    mAnchor = anchor;
    mPopup = new MenuPopupHelper(context, mMenu, anchor);
    mPopup.setCallback(this);
    mPopup.setForceShowIcon(true); //ADD THIS LINE

}

use the method setForceShowIcon to force it to show the icon. You can also just expose a public method to set this flag as well depending on your needs.

Madness answered 27/11, 2012 at 17:38 Comment(8)
You need to get into the internals in order to create your own PopupMenu, so this isn't such a great idea.Hawsehole
What should one do with the MenuBuilder or MenuPopupHelper classes?Condenser
If I recall, I was using the ActionSherlockLibrary so I used its equivalents of MenuBuilder and MenuPopupHelper.Madness
What's wrong with "getting in to the internals" on Android? That's the whole point of Android - this is not iOS. Great answer, thanks @Robert!Newsworthy
Nice idea, but some problems may occur when trying to mimic Android's popupMenu: it uses many internal (to OS) imports. So unless you feel like duplicating many interfaces/classes from Android internals, and lose any future popup improvements, it is not recommended.Despumate
@Madness Can you please publish the whole library? maybe via GIthub ?Nero
Here how to get instance of "MenuBuilder" in androidx?Passenger
@SonalBharwani Got any Solutions?Frutescent
D
74

Contribution to the solution provided by Gaelan Bolger. Use this code if you get a "IllegalAccessException: access to field not allowed".

PopupMenu popup = new PopupMenu(mContext, view);

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

prepareMenu(popup.getMenu());
popup.show();

text

Depositary answered 25/8, 2013 at 17:18 Comment(5)
I successfully used this to show the icon in my PopupMenu. Great. (Will be broken on android source changes, right?)Stow
This works well when proguard is disabled. When proguard is enabled, do you know what rules I should add in my proguard-project.txt?Ulick
I figure it out, I am not familiar to proguard configuration, just trial and error. After below 2 rules added, the code works again when proguard enabled. (Note, I am using PopupMenu from support package) -keepclassmembernames class android.support.v7.widget.PopupMenu { private android.support.v7.internal.view.menu.MenuPopupHelper mPopup; } -keepclassmembernames class android.support.v7.internal.view.menu.MenuPopupHelper { public void setForceShowIcon(boolean); }Ulick
Nice solution. I created my own PopupMenu that inherits from PopupMenu and does this nice refrection trick in the constructor. Only caveat: it may break, and stop showing images, in future Android versions.Despumate
It worked well in Android lollypop for me. Did not work in 4.2.2. Do not know what is the problem. ~just a heads up.Risteau
M
36

If you're willing to be a bit adventurous, look at Google's source code for PopupMenu. Create your own class i.e. MyPopupMenu that is the same as Google's PopupMenu class, but make one slight change.

In PopupMenu's constructor:

public MyPopupMenu(Context context, View anchor) {
    // TODO Theme?
    mContext = context;
    mMenu = new MenuBuilder(context);
    mMenu.setCallback(this);
    mAnchor = anchor;
    mPopup = new MenuPopupHelper(context, mMenu, anchor);
    mPopup.setCallback(this);
    mPopup.setForceShowIcon(true); //ADD THIS LINE

}

use the method setForceShowIcon to force it to show the icon. You can also just expose a public method to set this flag as well depending on your needs.

Madness answered 27/11, 2012 at 17:38 Comment(8)
You need to get into the internals in order to create your own PopupMenu, so this isn't such a great idea.Hawsehole
What should one do with the MenuBuilder or MenuPopupHelper classes?Condenser
If I recall, I was using the ActionSherlockLibrary so I used its equivalents of MenuBuilder and MenuPopupHelper.Madness
What's wrong with "getting in to the internals" on Android? That's the whole point of Android - this is not iOS. Great answer, thanks @Robert!Newsworthy
Nice idea, but some problems may occur when trying to mimic Android's popupMenu: it uses many internal (to OS) imports. So unless you feel like duplicating many interfaces/classes from Android internals, and lose any future popup improvements, it is not recommended.Despumate
@Madness Can you please publish the whole library? maybe via GIthub ?Nero
Here how to get instance of "MenuBuilder" in androidx?Passenger
@SonalBharwani Got any Solutions?Frutescent
T
21

We can use sub-menu model. So, we don't need to write method for showing popup menu, it will be showing automacally. Have a look:

menu.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>
</menu>

in 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;
}

The result is:

Result

Talc answered 20/11, 2013 at 11:29 Comment(2)
But you need two step to go to the submenu.Sediment
Most elegant solution!Handmedown
C
20

I was able to show the icons using reflection. It may not be the most elegant solution but it works.

            try {
                Class<?> classPopupMenu = Class.forName(popupMenu
                        .getClass().getName());
                Field mPopup = classPopupMenu.getDeclaredField("mPopup");
                mPopup.setAccessible(true);
                Object menuPopupHelper = mPopup.get(popupMenu);
                Class<?> classPopupHelper = Class.forName(menuPopupHelper
                        .getClass().getName());
                Method setForceIcons = classPopupHelper.getMethod(
                        "setForceShowIcon", boolean.class);
                setForceIcons.invoke(menuPopupHelper, true);
            } catch (Exception e) {
                e.printStackTrace();
            }
Cheesecloth answered 28/6, 2013 at 15:51 Comment(1)
make sure you call Field.setAccessible on the "mPopup" field.Epilimnion
S
14

before use method popup.show(),make a MenuPopupHelper instance and call method setForceShowIcon(true),like this

    try {
        Field mFieldPopup=popupMenu.getClass().getDeclaredField("mPopup");
        mFieldPopup.setAccessible(true);
        MenuPopupHelper mPopup = (MenuPopupHelper) mFieldPopup.get(popupMenu);
        mPopup.setForceShowIcon(true);
    } catch (Exception e) {

    }
Spurn answered 18/7, 2015 at 10:45 Comment(0)
A
14

The easiest way I found is that to use MenuBuilder and 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

Agueda answered 28/11, 2016 at 8:7 Comment(4)
This is the only solution that worked for me. Thanks!Johnston
@Johnston Me too.Agueda
Note, This will fail if your app theme has no actionbar.Wharfage
The only solution that worked for me, thank you.Razorbill
W
6

I found a native solution for this, using MenuPopupHelper.setForceShowIcon(true).

private void createMenu(int menuRes, View anchor, MenuBuilder.Callback callback) {
    Context context = anchor.getContext();

    NavigationMenu navigationMenu = new NavigationMenu(context);
    navigationMenu.setCallback(callback);

    SupportMenuInflater supportMenuInflater = new SupportMenuInflater(context);
    supportMenuInflater.inflate(menuRes, navigationMenu);

    MenuPopupHelper menuPopupHelper = new MenuPopupHelper(context, navigationMenu, anchor);
    menuPopupHelper.setForceShowIcon(true);
    menuPopupHelper.show();
}

Usage

private void initMenu(View view) {
    view.findViewById(R.id.myButton).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            createMenu(R.menu.help_menu, view, new MenuBuilder.Callback() {
                @Override
                public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
                    switch (item.getItemId()) {
                        case R.id.id1:
                            // Do something
                            break;
                        case R.id.id2:
                            // Do something
                            break;
                        case R.id.id3:
                            // Do something
                            break;
                    }
                    return true;
                }

                @Override
                public void onMenuModeChange(MenuBuilder menu) {

                }
            });
        }
    });
}
Wichern answered 24/3, 2016 at 10:13 Comment(0)
C
4

Along the line of using reflection and without the need to use MenuPopupHelper, you can add

    if (popup.getMenu() instanceof MenuBuilder) {
                //noinspection RestrictedApi
                ((MenuBuilder) popup.getMenu()).setOptionalIconsVisible(true);
            }

prior to inflating the menu

Costa answered 2/3, 2018 at 17:59 Comment(1)
Thank youuu I was trying it after inflating facepalmCommutative
G
4

You can use the setForceShowIcon (true)

PopupMenu(context, view).apply {
        setForceShowIcon(true)
        menuInflater.inflate(R.menu.menu_edit_professional_experience, menu)
        setOnMenuItemClickListener { item ->
            Toast.makeText(view.context, "YOU clcick", Toast.LENGTH_LONG).show()
            true
        }
    }.show()

Use setForceShowIcon(true)

Goodloe answered 16/4, 2021 at 16:59 Comment(0)
V
3

PopupMenu will not display icons. You can use an ActionBar.

http://developer.android.com/guide/topics/ui/actionbar.html

Virginavirginal answered 15/5, 2012 at 0:44 Comment(0)
C
3

Some of the solutions above will work with the reflection hack,

Just sharing this: I've recently came across the same issues, but I also wanted to create a more customized thing (adding custom view in the menu) so I created the following lib.

https://github.com/shehabic/Droppy

Chippewa answered 2/3, 2015 at 1:36 Comment(0)
P
2

If you're using AndroidX, which changed the visibility of MenuPopupHelper to package-private, you can avoid the cost of reflection by creating a wrapper class with the same package name.

This exposes package-private members to public.

package androidx.appcompat.widget // Create this package in your project's /src/main/java 

import android.annotation.SuppressLint

class PopupMenuWrapper(val t: PopupMenu) {
  
  @SuppressLint("RestrictedApi")
  fun setForceShowIcon(show: Boolean) { // Public method
    t.mPopup.setForceShowIcon(show)
  }
}

fun PopupMenu.wrap() = PopupMenuWrapper(this)

Then call the hidden function as you normally would.

val popup = PopupMenu(anchor.context, anchor)
popup.wrap().setForceShowIcon(true)
popup.show()
Polychromatic answered 21/6, 2020 at 20:45 Comment(0)
B
2

If you want to prevent using RestrictedApi use this extention function:

fun PopupMenu.forcePopUpMenuToShowIcons() {
    try {
        val method = menu.javaClass.getDeclaredMethod(
            "setOptionalIconsVisible",
            Boolean::class.javaPrimitiveType
        )
        method.isAccessible = true
        method.invoke(menu, true)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}
Bookworm answered 17/7, 2020 at 8:6 Comment(0)
F
1

The PopupMenu cannot be fully customized. Below you find a general solution to make your PopupMenu customizable via a custom layout. Having that, you can experiment a lot more with different layouts. Cheers.

1 - The Custom PopupMenu class:

public class PopupMenuCustomLayout {
    private PopupMenuCustomOnClickListener onClickListener;
    private Context context;
    private PopupWindow popupWindow;
    private int rLayoutId;
    private View popupView;

    public PopupMenuCustomLayout(Context context, int rLayoutId, PopupMenuCustomOnClickListener onClickListener) {
        this.context = context;
        this.onClickListener = onClickListener;
        this.rLayoutId = rLayoutId;
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
        popupView = inflater.inflate(rLayoutId, null);
        int width = LinearLayout.LayoutParams.WRAP_CONTENT;
        int height = LinearLayout.LayoutParams.WRAP_CONTENT;
        boolean focusable = true;
        popupWindow = new PopupWindow(popupView, width, height, focusable);
        popupWindow.setElevation(10);

        LinearLayout linearLayout = (LinearLayout) popupView;
        for (int i = 0; i < linearLayout.getChildCount(); i++) {
            View v = linearLayout.getChildAt(i);
            v.setOnClickListener( v1 -> { onClickListener.onClick( v1.getId()); popupWindow.dismiss(); });
        }
    }
    public void setAnimationStyle( int animationStyle) {
        popupWindow.setAnimationStyle(animationStyle);
    }
    public void show() {
        popupWindow.showAtLocation( popupView, Gravity.CENTER, 0, 0);
    }

    public void show( View anchorView, int gravity, int offsetX, int offsetY) {
        popupWindow.showAsDropDown( anchorView, 0, -2 * (anchorView.getHeight()));
    }

    public interface PopupMenuCustomOnClickListener {
        public void onClick(int menuItemId);
    }
}

2 - Your custom layout, e.g. linearlayout with horizontal layout. In this case I use a simple LinearLayout with TextView items. You can use Buttons, etc.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/popup_menu_custom_item_a"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="A"
        android:textAppearance="?android:textAppearanceMedium" />
    <TextView
        android:id="@+id/popup_menu_custom_item_b"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:text="B"
        android:textAppearance="?android:textAppearanceMedium" />
    // ...
</LinearLayout>

3 - Using the Custom PopupMenu like the normal PopupMenu.

PopupMenuCustomLayout popupMenu = new PopupMenuCustomLayout(
        MainActivity.mainActivity, R.layout.popup_menu_custom_layout,
        new PopupMenuCustomLayout.PopupMenuCustomOnClickListener() {
            @Override
            public void onClick(int itemId) {
                // log statement: "Clicked on: " + itemId
                switch (itemId) {
                    case R.id.popup_menu_custom_item_a:
                        // log statement: "Item A was clicked!"
                        break;
                }
            }
        });
// Method 1: popupMenu.show();
// Method 2: via an anchor view: 
popupMenu.show( anchorView, Gravity.CENTER, 0, 0);
Farlie answered 15/12, 2019 at 15:59 Comment(0)
A
0

Just add this line:

 popup.setForceShowIcon(true);

Simply do this:

            PopupMenu popup = new PopupMenu(context, view);
            popup.inflate(R.menu.chat_tile_menu);
            popup.setOnMenuItemClickListener(item -> {
               // menu 
                return true;
            });
   /*--->*/ popup.setForceShowIcon(true);
            popup.show();
Aeriel answered 6/3, 2023 at 19:9 Comment(0)
N
0

Sadly Google doesn't provide an official way to do it for various kinds of popups, for all Android versions, even though it shows as if it's possible right on their documentation of contextual-menu:

https://developer.android.com/develop/ui/views/components/menus#context-menu

This is why I've created requests about this on the issue tracker. Please consider starring them:

As for workarounds for now, when done on Toolbar, you can try this, but sadly it's not official yet:

@SuppressLint("RestrictedApi")
@UiThread
override fun onCreateOptionsMenu(menu: Menu): Boolean {
    menuInflater.inflate(R.menu.menu_main, menu)
    (menu as? MenuBuilder)?.setOptionalIconsVisible(true)
    return true
}

And, when on PopupMenu, you can use this:

@UiThread
fun setForceShowIconsOnPopupMenu(popupMenu: PopupMenu, enable: Boolean) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        popupMenu.setForceShowIcon(enable)
        return
    }
    try {
        val classPopupMenu = Class.forName(popupMenu.javaClass.name)
        val declaredField = classPopupMenu.getDeclaredField("mPopup")
        declaredField.isAccessible = true
        val menuPopupHelper = declaredField.get(popupMenu)
        val classPopupHelper = Class.forName(menuPopupHelper.javaClass.name)
        val setForceIcons =
                classPopupHelper.getMethod("setForceShowIcon", Boolean::class.javaPrimitiveType!!)
        setForceIcons.invoke(menuPopupHelper, enable)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}
Nero answered 9/11, 2023 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.