PopupMenu带图标

当然,我们正在处理SDK 11及以上版本。

我打算做这样的事情: 在这里输入图像说明

PopupMenu每个项目旁边,我想放置一个图标

我创build了一个XML文件,并将其放在/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> 

正如你注意到的,在XML文件中,我正在定义我想要的图标,但是当popup菜单显示时,它将显示没有图标的图标。 我该怎么做才能让这两个图标出现?

Solutions Collecting From Web of "PopupMenu带图标"

否则我会执行它:

创build一个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> 

然后在你的Activity创buildPopUpWindow

  // 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, px + OFFSET_X, py + OFFSET_Y); } 

最后popup一个button或其他任何东西:

  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] = yvgetLocationOnScreen(location); //Initialize the Point with x, and y positions point = new Point(); point.x = location[0]; point.y = location[1]; showStatusPopup(TasksListActivity.this, point); } }); 

PopUpWindow好例子:

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

如果你使用的是AppCompat v7,这种方法是有效的。 这是一个有点hacky,但明显好于使用reflection,让你仍然使用核心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 /菜单/ 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> 

这会导致使用菜单资源文件中定义的图标的popup菜单:

在这里输入图像说明

Android的popup菜单有一个隐藏的方法来显示菜单图标。 使用Javareflection来启用它,如下面的代码片段。

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

阅读PopupMenu源代码。 我们可以通过下面的代码显示图标:

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

但MenuPopupHelper.java是在android内部包。 所以我们应该使用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(); 

我解决了我的问题,有史以来最简单的方法,从来没有料到这么简单:

在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> 

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

这是一个使用子菜单的伎俩

用图标使用MenuBuilderMenuPopupHelperpopup菜单

  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> 

在这里输入图像说明

如果你想在popup菜单中显示图标,看看https://github.com/shehabic/Droppy ,这是非常酷,易于使用

/ res / menu目录下的list_item_menu.xml

 <?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> 

在我的活动

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

结果

弹出菜单

基于@Ajay的答案…这是我做的

  @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> 

截图

在这里输入图像说明

AppCompat中的MenuPopupHelper类具有@hide注释。 如果这是一个问题,或者因为任何原因无法使用AppCompat,还有另一种解决scheme,在MenuItem标题中使用Spannable ,它包含图标和标题文本。

主要步骤是:

  • 用一个menu xml文件膨胀你的PopupMenu
  • 如果任何一个项目有一个图标,那么对所有的项目做这个:
    • 如果该项目没有图标,请创build一个透明图标。 这确保没有图标的项目将与具有图标的项目alignment
    • 创build一个包含图标和标题的SpannableStringBuilder
    • 将菜单项的标题设置为SpannableStringBuilder
    • 设置菜单项的图标为空,“以防万一”

优点:没有反思。 不使用任何隐藏的apis。 可以使用框架PopupMenu。

缺点:更多的代码。 如果你有一个没有图标的子菜单,它会在小屏幕上留下不需要的填充。


细节:

首先,为一个dimens.xml文件中的图标定义一个大小:

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

然后,一些方法将xml中定义的图标移动到标题中:

 /** * 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); } 

最后,创buildPopupMenu并在显示之前使用上述方法:

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

截图: 截图

我正在尝试@Stephen Kidson的回答和@ david.schereiber的build议,并且我意识到在MenuBuilder没有setOnMenuItemClickListener方法。 有一些v7的源代码,我发现这个解决scheme:

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

你可以实现这个通过使用reflection,如果你不熟悉它,借助这个可怕的java高级特性,你可以修改在JVM中运行的应用程序的运行时行为,你可以查看对象并在运行时执行它的方法在我们的例子中,我们需要在运行时修改popupMenu的行为,而不是扩展核心类并修改它;)希望有帮助

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