Android 是否可以在弹出菜单中显示图标?

Android 是否可以在弹出菜单中显示图标?,android,android-3.0-honeycomb,Android,Android 3.0 Honeycomb,我真的很喜欢3.0中的新弹出菜单,但我就是不能在菜单项旁边显示任何图标。我正在从下面的.xml扩展菜单: 我无法显示图标,我缺少什么吗?弹出菜单将不显示图标。您可以使用ActionBar 如果你愿意冒险一点,看看谷歌的弹出菜单源代码。创建您自己的类,即MyPopupMenu,它与Google的PopupMenu类相同,但做一点小小的更改 在弹出菜单的构造函数中: public MyPopupMenu(Context context, View anchor) { // TODO Th

我真的很喜欢3.0中的新弹出菜单,但我就是不能在菜单项旁边显示任何图标。我正在从下面的.xml扩展菜单:


我无法显示图标,我缺少什么吗?

弹出菜单将不显示图标。您可以使用ActionBar


如果你愿意冒险一点,看看谷歌的弹出菜单源代码。创建您自己的类,即MyPopupMenu,它与Google的PopupMenu类相同,但做一点小小的更改

在弹出菜单的构造函数中:

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

}

使用方法setForceShowIcon强制它显示图标。您也可以根据需要公开一个公共方法来设置此标志。

我可以使用反射来显示图标。这可能不是最优雅的解决方案,但它确实有效

            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();
            }
试试看{
Class classPopupMenu=Class.forName(popupMenu
.getClass().getName());
字段mPopup=classPopupMenu.getDeclaredField(“mPopup”);
mPopup.setAccessible(true);
对象菜单opupPhelper=mPopup.get(弹出菜单);
Class CLASSPOPUSCURLPER=Class.forName(menuPopupHelper
.getClass().getName());
方法setForceIcons=classPopucUpCelper.getMethod(
“setForceShowIcon”,boolean.class);
调用(menuPopupHelper,true);
}捕获(例外e){
e、 printStackTrace();
}

对盖兰·博尔格提供的解决方案的贡献。 如果您得到“IllegalAccessException:不允许访问字段”,请使用此代码

PopupMenu popup=新的PopupMenu(mContext,视图);
试一试{
Field[]fields=popup.getClass().getDeclaredFields();
用于(字段:字段){
if(“mPopup.equals(field.getName())){
字段。setAccessible(true);
Object menuPopupHelper=field.get(弹出窗口);
Class CLASSPOPUSCURLPER=Class.forName(menuPopupHelper
.getClass().getName());
方法setForceIcons=classPopucUpCelper.getMethod(
“setForceShowIcon”,boolean.class);
调用(menuPopupHelper,true);
打破
}
}
}捕获(例外e){
e、 printStackTrace();
}
prepareMenu(popup.getMenu());
popup.show();

text

我们可以使用子菜单模型。所以,我们不需要编写显示弹出菜单的方法,它将自动显示。看看:

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>
<?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.show()之前,创建一个MenuPopupHelper实例并调用方法setForceShowIcon(true),如下所示

    try {
        Field mFieldPopup=popupMenu.getClass().getDeclaredField("mPopup");
        mFieldPopup.setAccessible(true);
        MenuPopupHelper mPopup = (MenuPopupHelper) mFieldPopup.get(popupMenu);
        mPopup.setForceShowIcon(true);
    } catch (Exception e) {

    }

我使用
菜单opuphelper.setForceShowIcon(true)
找到了一个本机解决方案

用法
我发现最简单的方法是使用
MenuBuilder
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

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


在使用反射的过程中,您可以添加

    if (popup.getMenu() instanceof MenuBuilder) {
                //noinspection RestrictedApi
                ((MenuBuilder) popup.getMenu()).setOptionalIconsVisible(true);
            }

在弹出菜单之前,无法完全自定义弹出菜单。在下面,您可以找到一个通用的解决方案,使您的弹出菜单可以通过自定义布局进行自定义。有了这些,你可以用不同的布局做更多的实验。干杯

1-自定义弹出菜单类:

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

如果您使用的是AndroidX,它将
MenuPopupHelper
的可见性更改为package private,那么您可以通过创建具有相同包名的包装器类来避免反射成本

这将向公众公开包私有成员

package androidx.appcompat.widget//在项目的/src/main/java中创建此包
导入android.annotation.SuppressLint
类PopupMenuRapper(val t:PopupMenu){
@SuppressLint(“RestrictedApi”)
fun setForceShowIcon(show:Boolean){//Public方法
t、 mPopup.setForceShowIcon(显示)
}
}
fun PopupMenu.wrap()=PopupMenuWrapper(此)
然后像平常一样调用隐藏函数

val popup=popup菜单(anchor.context,anchor)
popup.wrap().setForceShowIcon(真)
popup.show()

如果要防止使用
受限API
请使用此扩展功能:

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

您可以使用setForceShowIcon(真)


使用setForceShowIcon(true)

为了创建自己的弹出菜单,您需要进入内部,所以这不是一个好主意。应该如何处理MenuBuilder或MenuPoupHelper类?如果我记得,我使用的是ActionSherlockLibrary,所以我使用了MenuBuilder和MenuPopupHelper的等价物。Android上的“深入内部”有什么不对?这就是安卓的全部要点——这不是iOS。回答得很好,谢谢@Robert!如何在androidx中获取“MenuBuilder”实例?请确保在“mPopup”字段中调用Field.setAccessible。我成功地使用它在我的
弹出菜单中显示图标。伟大的(将在android源代码更改时中断,对吗?)禁用proguard时,此功能运行良好。启用proguard后,您知道我应该在proguard project.txt中添加哪些规则吗?我想,我不熟悉proguard的配置,只是反复尝试。添加以下2条规则后,当启用proguard时,代码再次工作。(注意,我使用的是support package中的PopupMenu)-keepclassmembernames类android.support.v7.widget.PopupMenu{private android.support.v7.internal.view.menu.MenuPopupPer MPoppup;}-keepclassmembernames类android.support.v7.internal.view.menu.MenupOpupPer{public void setForceShowIcon(布尔);}很好的解决方案。
<?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>
    if (popup.getMenu() instanceof MenuBuilder) {
                //noinspection RestrictedApi
                ((MenuBuilder) popup.getMenu()).setOptionalIconsVisible(true);
            }
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);
    }
}
<?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>
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);
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()
    }
}
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()