带图标的 PopupMenu

当然,我们在这里处理的是 SDK 11及以上版本。

我打算做类似的事情: enter image description here

在每个 项目的旁边,在那个 PopupMenu中,我想放置一个 偶像

我创建了一个 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 文件中,我定义了我想要的图标,但是,当弹出菜单显示时,它显示的是没有图标的图标。我应该怎样做才能使这两个图标出现?

100393 次浏览

否则我会实施它:

创建 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中创建 PopUpWindow:

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

最后弹出一个按钮或其他东西的 onClick:

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

PopUpWindow的一个好例子:

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

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

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

这是一个使用 子菜单的把戏

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

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

但是 MenuPopupHelper.java 在 android 内部包中,所以我们应该使用反射:

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

如果你想在弹出菜单中显示图标,看看 https://github.com/shehabic/Droppy,它很酷,也很容易使用

Android 弹出菜单有一个显示菜单图标的隐藏方法。使用 Java 反射来启用它,如下面的代码片段所示。

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

如果您正在使用 AppCompat v7,这种方法就可以工作。它有点粗糙,但是 意义重大比使用反射更好,并且让你仍然可以使用核心的 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>


这将导致弹出菜单使用菜单资源文件中定义的图标:

enter image description here

我试着“ Stephen Kidson 的回答”和“ David”。我意识到在 MenuBuilder中没有这样的方法 setOnMenuItemClickListener。对 v7的源代码稍作修改后,我找到了这个解决方案:

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

弹出菜单与图标使用 MenuBuilderMenuPopupHelper

    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

基于@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);
}
}

弹出

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

截图

enter image description here

AppCompat 中的 MenuPopupHelper类具有 @hide注释。如果这是一个问题,或者出于某种原因不能使用 AppCompat,那么在 MenuItem标题中使用 Spannable还有另一个解决方案,它包含图标和标题文本。

主要步骤如下:

  • 使用 menu xml 文件充满 PopupMenu
  • 如果任何项目有一个图标,然后这样做的 所有的项目:
    • 如果项目没有图标,创建一个透明图标。这样可以确保不带图标的项目与带图标的项目对齐
    • 创建一个包含图标和标题的 SpannableStringBuilder
    • 将菜单项的标题设置为 SpannableStringBuilder
    • 将菜单项的图标设置为 null,“以防万一”

优点: 没有反射。不使用任何隐藏的 api。可以使用框架弹出菜单。

缺点: 更多代码。如果您有一个子菜单没有图标,它将有不必要的左填充在一个小屏幕上。


详情:

首先,在 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);
}

最后,创建你的 PopupMenu,在显示它之前使用上面的方法:

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

截图: screenshot

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

结果

popup_menu

你可以通过使用反射来实现这一点,如果你不熟悉它,借助这个了不起的 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();








}