The MenuPopupHelper
class in AppCompat has the @hide
annotation. If that's a concern, or if you can't use AppCompat for whatever reason, there's another solution using a Spannable
in the MenuItem
title which contains both the icon and the title text.
The main steps are:
- inflate your
PopupMenu
with a menu
xml file
- if any of the items have an icon, then do this for all of the items:
- if the item doesn't have an icon, create a transparent icon. This ensures items without icons will be aligned with items with icons
- create a
SpannableStringBuilder
containing the icon and title
- set the menu item's title to the
SpannableStringBuilder
- set the menu item's icon to null, "just in case"
Pros: No reflection. Doesn't use any hidden apis. Can work with the framework PopupMenu.
Cons: More code. If you have a submenu without an icon, it will have unwanted left padding on a small screen.
Details:
First, define a size for the icon in a dimens.xml
file:
<dimen name="menu_item_icon_size">24dp</dimen>
Then, some methods to move the icons defined in xml into the titles:
/**
* 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);
}
Finally, create your PopupMenu and use the above methods before showing it:
PopupMenu popupMenu = new PopupMenu(view.getContext(), view);
popupMenu.inflate(R.menu.popup_menu);
insertMenuItemIcons(textView.getContext(), popupMenu);
popupMenu.show();
Screenshot: