Popup menu with icon on Android

25,567

Solution 1

You can create popup menu with icon using the 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) {}
});

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

Solution 2

You can enable icons for popup menu by using Java reflection to call a hidden method as below:

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();
    }
}

Solution 3

You could use reflection as described here: https://stackoverflow.com/a/18431605/4521603

Or if you use Xamarin / C#:

add:

using Java.Lang.Reflect;

Then use this in your code:

PopupMenu puMenu = new PopupMenu(Activity, v)

Field field = puMenu.Class.GetDeclaredField("mPopup");
field.Accessible = true;
Java.Lang.Object menuPopupHelper = field.Get(puMenu);
Method setForceIcons = menuPopupHelper.Class.GetDeclaredMethod("setForceShowIcon", Java.Lang.Boolean.Type);
setForceIcons.Invoke(menuPopupHelper, true);

puMenu.Inflate (Resource.Menu.your_actions);
puMenu.Show ();

Solution 4

Use this:

/**
 * Copied from android.support.v7.widget.PopupMenu.
 * "mPopup.setForceShowIcon(true);" in the constructor does the trick :)
 * 
 * @author maikvlcek
 * @since 5:00 PM - 1/27/14
 */
public class IconizedMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
        private Context mContext;
        private MenuBuilder mMenu;
        private View mAnchor;
        private MenuPopupHelper mPopup;
        private OnMenuItemClickListener mMenuItemClickListener;
        private OnDismissListener mDismissListener;

        /**
         * Callback interface used to notify the application that the menu has closed.
         */
        public interface OnDismissListener {
            /**
             * Called when the associated menu has been dismissed.
             *
             * @param menu The PopupMenu that was dismissed.
             */
            public void onDismiss(IconizedMenu menu);
        }

        /**
         * Construct a new PopupMenu.
         *
         * @param context Context for the PopupMenu.
         * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
         *               is room, or above it if there is not.
         */
        public IconizedMenu(Context context, View anchor) {
            mContext = context;
            mMenu = new MenuBuilder(context);
            mMenu.setCallback(this);
            mAnchor = anchor;
            mPopup = new MenuPopupHelper(context, mMenu, anchor);
            mPopup.setCallback(this);
            mPopup.setForceShowIcon(true);
        }

        /**
         * @return the {@link android.view.Menu} associated with this popup. Populate the returned Menu with
         * items before calling {@link #show()}.
         *
         * @see #show()
         * @see #getMenuInflater()
         */
        public Menu getMenu() {
            return mMenu;
        }

        /**
         * @return a {@link android.view.MenuInflater} that can be used to inflate menu items from XML into the
         * menu returned by {@link #getMenu()}.
         *
         * @see #getMenu()
         */
        public MenuInflater getMenuInflater() {
            return new SupportMenuInflater(mContext);
        }

        /**
         * Inflate a menu resource into this PopupMenu. This is equivalent to calling
         * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
         * @param menuRes Menu resource to inflate
         */
        public void inflate(int menuRes) {
            getMenuInflater().inflate(menuRes, mMenu);
        }

        /**
         * Show the menu popup anchored to the view specified during construction.
         * @see #dismiss()
         */
        public void show() {
            mPopup.show();
        }

        /**
         * Dismiss the menu popup.
         * @see #show()
         */
        public void dismiss() {
            mPopup.dismiss();
        }

        /**
         * Set a listener that will be notified when the user selects an item from the menu.
         *
         * @param listener Listener to notify
         */
        public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
            mMenuItemClickListener = listener;
        }

        /**
         * Set a listener that will be notified when this menu is dismissed.
         *
         * @param listener Listener to notify
         */
        public void setOnDismissListener(OnDismissListener listener) {
            mDismissListener = listener;
        }

        /**
         * @hide
         */
        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
            if (mMenuItemClickListener != null) {
                return mMenuItemClickListener.onMenuItemClick(item);
            }
            return false;
        }

        /**
         * @hide
         */
        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
            if (mDismissListener != null) {
                mDismissListener.onDismiss(this);
            }
        }

        /**
         * @hide
         */
        public boolean onOpenSubMenu(MenuBuilder subMenu) {
            if (subMenu == null) return false;

            if (!subMenu.hasVisibleItems()) {
                return true;
            }

            // Current menu will be dismissed by the normal helper, submenu will be shown in its place.
            new MenuPopupHelper(mContext, subMenu, mAnchor).show();
            return true;
        }

        /**
         * @hide
         */
        public void onCloseSubMenu(SubMenuBuilder menu) {
        }

        /**
         * @hide
         */
        public void onMenuModeChange(MenuBuilder menu) {
        }

        /**
         * Interface responsible for receiving menu item click events if the items themselves
         * do not have individual item click listeners.
         */
        public interface OnMenuItemClickListener {
            /**
             * This method will be invoked when a menu item is clicked if the item itself did
             * not already handle the event.
             *
             * @param item {@link MenuItem} that was clicked
             * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
             */
            public boolean onMenuItemClick(MenuItem item);
        }

}

Source: https://gist.github.com/mediavrog/9345938

Solution 5

It's because when you use the showAsAction="never" attribute, the default overflow does not return your icon. You could create your own overflow like this:

<item android:title=""
        android:id="@+id/overflow"
        android:showAsAction="always"
        android:icon="@drawable/overflow_icon">
  <menu >    
    <item android:id="@+id/back"
          android:icon="@drawable/back1"
          android:title="Back" />

    <item android:id="@+id/My_Profile"
          android:icon="@drawable/myprofile"
          android:title="My Profile" />

    <item android:id="@+id/Job_Alert"
          android:icon="@drawable/jobalert4"
          android:title="Job Alert !" />

    <item android:id="@+id/saved_job"
          android:icon="@drawable/jobapplied"
          android:title="Saved Job"/>
    <item android:id="@+id/Logout"
          android:icon="@drawable/logout"
          android:title="Logout" /> 
  </menu>
</item>
Share:
25,567
Kuldeep
Author by

Kuldeep

Updated on September 18, 2020

Comments

  • Kuldeep
    Kuldeep over 3 years

    My menu xml code menu.xml:

    <?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Single menu item 
         Set id, icon and Title for each menu item
    -->
    
     <item android:id="@+id/back"
          android:icon="@drawable/back1"
            android:showAsAction="never"
          android:title="Back" />
    
    <item android:id="@+id/My_Profile"
          android:icon="@drawable/myprofile"
           android:showAsAction="never"
          android:title="My Profile" />
    
    <item android:id="@+id/Job_Alert"
          android:icon="@drawable/jobalert4"
           android:showAsAction="never"
          android:title="Job Alert !" />
    
    <item android:id="@+id/saved_job"
          android:icon="@drawable/jobapplied"
          android:title="Saved Jobs"
           />
    
    
    <item android:id="@+id/Logout"
          android:icon="@drawable/logout"
          android:title="Logout" /> 
    </menu>
    

    I am calling menu xml like this

         PopupMenu popup = new PopupMenu(getBaseContext(), v);
     popup.getMenuInflater().inflate(R.menu.menu,  popup.getMenu());
         popup.show();
    

    But it does not show the icon.

    How can I set the icon on the popup menu?

  • Saurabh
    Saurabh about 9 years
    I tried using this class and got an exception while showing popup menu. java.lang.RuntimeException: Failed to resolve attribute at index 6
  • iaindownie
    iaindownie about 7 years
    +1 for neatest way I've seen of building an icon + text menu item. However, how have you handled the onItemClick method?
  • Ajay Sivan
    Ajay Sivan about 7 years
    @iaindownie I have added code to handle menu clicks. I think this may help you.
  • iaindownie
    iaindownie about 7 years
    Brilliant, thanks! This should be the answer on all the other posts too! Far cleaner than everything else I've seen for custom/icon menus.
  • Amal
    Amal about 7 years
    Usage of this class, as mentioned by @kevinkl3 on comment : public void showPopup(View v) { IconizedMenu popup = new IconizedMenu(this, v); MenuInflater inflater = popup.getMenuInflater(); inflater.inflate(R.menu.menu_myactivity, popup.getMenu()); popup.show(); }
  • Michael Kazarian
    Michael Kazarian over 6 years
    I have caught MenuBuilder constructor can only be called from within the same library group.