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"
an开发者_开发百科droid: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?
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
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.
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:
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();
}
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) {
}
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>
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) {
}
});
}
});
}
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
PopupMenu will not display icons. You can use an ActionBar.
http://developer.android.com/guide/topics/ui/actionbar.html
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
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()
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()
}
}
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)
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);
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();
精彩评论