首页 文章

如何更改选项菜单的背景颜色?

提问于
浏览
82

我正在尝试更改选项菜单的默认颜色:白色:我希望选项菜单上的每个项目都有黑色背景 .

我在菜单元素中的item元素上尝试了一些类似android:itemBackground =“#000000”的镜头,但它没有用 .

我怎么能做到这一点?

12 回答

  • 49

    谢谢马库斯!它通过修复一些语法错误顺利地工作在2.3,这里是固定代码

    protected void setMenuBackground() {
        getLayoutInflater().setFactory(new Factory() {
    
            @Override
            public View onCreateView(final String name, final Context context,
                    final AttributeSet attrs) {
    
                if (name.equalsIgnoreCase("com.android.internal.view.menu.IconMenuItemView")) {
    
                    try { // Ask our inflater to create the view
                        final LayoutInflater f = getLayoutInflater();
                        final View[] view = new View[1];
                        try {
                            view[0] = f.createView(name, null, attrs);
                        } catch (InflateException e) {
                            hackAndroid23(name, attrs, f, view);
                        }
                        // Kind of apply our own background
                        new Handler().post(new Runnable() {
                            public void run() {
                                view[0].setBackgroundColor(Color.WHITE);
    
                            }
                        });
                        return view[0];
                    } catch (InflateException e) {
                    } catch (ClassNotFoundException e) {
    
                    }
                }
                return null;
            }
        });
    }
    
    static void hackAndroid23(final String name,
            final android.util.AttributeSet attrs, final LayoutInflater f,
            final View[] view) {
        // mConstructorArgs[0] is only non-null during a running call to
        // inflate()
        // so we make a call to inflate() and inside that call our dully
        // XmlPullParser get's called
        // and inside that it will work to call
        // "f.createView( name, null, attrs );"!
        try {
            f.inflate(new XmlPullParser() {
                @Override
                public int next() throws XmlPullParserException, IOException {
                    try {
                        view[0] = (TextView) f.createView(name, null, attrs);
                    } catch (InflateException e) {
                    } catch (ClassNotFoundException e) {
                    }
                    throw new XmlPullParserException("exit");
                }
            }, null, false);
        } catch (InflateException e1) {
            // "exit" ignored
        }
    }
    
  • 0

    这显然是许多程序员所拥有的问题,Google尚未提供令人满意的支持解决方案 .

    关于这个主题的帖子有很多交叉的意图和误解,所以请在回答之前阅读整个答案 .

    下面我在本页的其他答案中包含了一个更“精致”和评论很好的黑客版本,还包含了这些非常密切相关的问题的想法:

    Change background color of android menu

    How to change the background color of the options menu?

    Android: customize application's menu (e.g background color)

    http://www.macadamian.com/blog/post/android_-_theming_the_unthemable/

    Android MenuItem Toggle Button

    Is it possible to make the Android options menu background non-translucent?

    http://www.codeproject.com/KB/android/AndroidMenusMyWay.aspx

    Setting the menu background to be opaque

    我在2.1(模拟器),2.2(2个真实设备)和2.3(2个真实设备)上测试了这个黑客 . 我还没有任何3.X平板电脑可供测试,但如果我这样做,我会在这里发布任何所需的更改 . 鉴于3.X平板电脑使用操作栏而不是选项菜单,如下所述:

    http://developer.android.com/guide/topics/ui/menus.html#options-menu

    这个黑客几乎肯定会对3.X平板电脑无所作为(无害和无益) .

    问题陈述(在触发回复之前阅读此内容并带有否定评论):

    “选项”菜单在不同设备上具有完全不同的样式 . 纯黑色与白色文本上的一些,纯白色与黑色文本上的一些 . 我和许多其他开发人员希望控制选项菜单单元格 as well as the color of the Options menu text 的背景颜色 .

    某些应用程序开发人员只需要设置单元格背景颜色(而不是文本颜色),他们可以使用另一个答案中描述的android:panelFullBackground样式以更干净的方式执行此操作 . 但是,目前无法使用样式控制“选项”菜单文本颜色,因此只能使用此方法将背景更改为不会使文本“消失”的另一种颜色 .

    我们希望通过一个记录在案的,面向未来的解决方案来做到这一点,但是从Android <= 2.3开始就无法使用 . 因此,我们必须使用适用于当前版本的解决方案,旨在最大限度地减少未来版本中崩溃/破坏的可能性 . 如果必须失败,我们希望解决方案无法正常返回默认行为 .

    有许多合理的原因可能需要控制选项菜单的外观(通常是为了匹配应用程序其余部分的视觉样式),所以我不会详述 .

    有一个谷歌Android错误发布了这个:请通过主演这个错误添加你的支持(注意谷歌不鼓励“我也是”评论:只是一个明星就足够了):

    http://code.google.com/p/android/issues/detail?id=4441

    解决方案摘要:

    几张海报提出了一个涉及LayoutInflater.Factory的黑客攻击 . 建议的hack适用于Android <= 2.2而Android 2.3失败,因为黑客做了一个没有记录的假设:可以直接调用LayoutInflater.getView(),而不是在同一个LayoutInflater实例上调用LayoutInflater.inflate() . Android 2.3中的新代码破坏了这一假设并导致了NullPointerException .

    我下面稍微改进的黑客不依赖于这个假设 .

    此外,黑客还依赖于使用内部的,未记录的类名“com.android.internal.view.menu.IconMenuItemView”作为字符串(而不是Java类型) . 我认为没有办法避免这种情况,仍然可以实现既定目标 . 但是,如果当前系统上没有出现“com.android.internal.view.menu.IconMenuItemView”,则可能会以谨慎的方式进行黑客攻击 .

    再次,了解这是一个黑客,我绝不是声称这将适用于所有平台 . 但是我们的开发人员并没有生活在一个幻想的学术世界里,一切都必须在书中:我们有一个问题要解决,我们必须尽可能地解决它 . 例如,“com.android.internal.view.menu.IconMenuItemView”似乎不太可能存在于3.X平板电脑上,因为它们使用操作栏而不是选项菜单 .

    最后,一些开发人员通过完全抑制Android选项菜单并编写自己的菜单类来解决这个问题(参见上面的一些链接) . 我没有试过这个,但是如果你有时间编写自己的View并想出如何替换Android的视图(我确定这里的细节中有魔鬼)那么它可能是一个不需要任何解决方案的好方法没有证件的黑客 .

    HACK:

    这是代码 .

    要使用此代码,请从您的活动onCreate()或您的活动onCreateOptionsMenu()调用addOptionsMenuHackerInflaterFactory()ONCE . 它设置了一个默认工厂,它将影响后续创建任何选项菜单 . 它不会影响已经创建的选项菜单(之前的hacks使用了setMenuBackground()的函数名称,这是非常误导的该函数在返回之前不会设置任何菜单属性) .

    @SuppressWarnings("rawtypes")
    static Class       IconMenuItemView_class = null;
    @SuppressWarnings("rawtypes")
    static Constructor IconMenuItemView_constructor = null;
    
    // standard signature of constructor expected by inflater of all View classes
    @SuppressWarnings("rawtypes")
    private static final Class[] standard_inflater_constructor_signature = 
    new Class[] { Context.class, AttributeSet.class };
    
    protected void addOptionsMenuHackerInflaterFactory()
    {
        final LayoutInflater infl = getLayoutInflater();
    
        infl.setFactory(new Factory()
        {
            public View onCreateView(final String name, 
                                     final Context context,
                                     final AttributeSet attrs)
            {
                if (!name.equalsIgnoreCase("com.android.internal.view.menu.IconMenuItemView"))
                    return null; // use normal inflater
    
                View view = null;
    
                // "com.android.internal.view.menu.IconMenuItemView" 
                // - is the name of an internal Java class 
                //   - that exists in Android <= 3.2 and possibly beyond
                //   - that may or may not exist in other Android revs
                // - is the class whose instance we want to modify to set background etc.
                // - is the class we want to instantiate with the standard constructor:
                //     IconMenuItemView(context, attrs)
                // - this is what the LayoutInflater does if we return null
                // - unfortunately we cannot just call:
                //     infl.createView(name, null, attrs);
                //   here because on Android 3.2 (and possibly later):
                //   1. createView() can only be called inside inflate(),
                //      because inflate() sets the context parameter ultimately
                //      passed to the IconMenuItemView constructor's first arg,
                //      storing it in a LayoutInflater instance variable.
                //   2. we are inside inflate(),
                //   3. BUT from a different instance of LayoutInflater (not infl)
                //   4. there is no way to get access to the actual instance being used
                // - so we must do what createView() would have done for us
                //
                if (IconMenuItemView_class == null)
                {
                    try
                    {
                        IconMenuItemView_class = getClassLoader().loadClass(name);
                    }
                    catch (ClassNotFoundException e)
                    {
                        // this OS does not have IconMenuItemView - fail gracefully
                        return null; // hack failed: use normal inflater
                    }
                }
                if (IconMenuItemView_class == null)
                    return null; // hack failed: use normal inflater
    
                if (IconMenuItemView_constructor == null)
                {
                    try
                    {
                        IconMenuItemView_constructor = 
                        IconMenuItemView_class.getConstructor(standard_inflater_constructor_signature);
                    }
                    catch (SecurityException e)
                    {
                        return null; // hack failed: use normal inflater
                    }
                    catch (NoSuchMethodException e)
                    {
                        return null; // hack failed: use normal inflater
                    }
                }
                if (IconMenuItemView_constructor == null)
                    return null; // hack failed: use normal inflater
    
                try
                {
                    Object[] args = new Object[] { context, attrs };
                    view = (View)(IconMenuItemView_constructor.newInstance(args));
                }
                catch (IllegalArgumentException e)
                {
                    return null; // hack failed: use normal inflater
                }
                catch (InstantiationException e)
                {
                    return null; // hack failed: use normal inflater
                }
                catch (IllegalAccessException e)
                {
                    return null; // hack failed: use normal inflater
                }
                catch (InvocationTargetException e)
                {
                    return null; // hack failed: use normal inflater
                }
                if (null == view) // in theory handled above, but be safe... 
                    return null; // hack failed: use normal inflater
    
    
                // apply our own View settings after we get back to runloop
                // - android will overwrite almost any setting we make now
                final View v = view;
                new Handler().post(new Runnable()
                {
                    public void run()
                    {
                        v.setBackgroundColor(Color.BLACK);
    
                        try
                        {
                            // in Android <= 3.2, IconMenuItemView implemented with TextView
                            // guard against possible future change in implementation
                            TextView tv = (TextView)v;
                            tv.setTextColor(Color.WHITE);
                        }
                        catch (ClassCastException e)
                        {
                            // hack failed: do not set TextView attributes
                        }
                    }
                });
    
                return view;
            }
        });
    }
    

    感谢阅读和享受!

  • 14

    菜单背景的style属性是 android:panelFullBackground .

    尽管文档说的是,它需要是一个资源(例如 @android:color/black@drawable/my_drawable ),如果直接使用颜色值,它将崩溃 .

    这也将摆脱我无法使用primalpop解决方案更改或删除的项目边框 .

    至于文本颜色,我还没有找到任何方法来设置2.2中的样式,我确信我已经尝试了一切(这是我发现菜单背景属性的方式) . 你需要使用primalpop的解决方案 .

  • 47

    对于Android 2.3,这可以通过一些非常重的黑客来完成:

    Android 2.3问题的根本原因是在LayoutInflater中,mConstructorArgs [0] = mContext仅在运行调用期间设置

    http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/view/LayoutInflater.java/#352

    protected void setMenuBackground(){
        getLayoutInflater().setFactory( new Factory() {
    
    
        @Override
        public View onCreateView (final String name, final Context context, final AttributeSet attrs ) {
    
            if ( name.equalsIgnoreCase( "com.android.internal.view.menu.IconMenuItemView" ) ) {
    
                try { // Ask our inflater to create the view
                    final LayoutInflater f = getLayoutInflater();
                    final View[] view = new View[1]:
                    try {
                       view[0] = f.createView( name, null, attrs );
                    } catch (InflateException e) {
               hackAndroid23(name, attrs, f, view);
                        }
                    // Kind of apply our own background
                    new Handler().post( new Runnable() {
                        public void run () {
                        view.setBackgroundResource( R.drawable.gray_gradient_background);
    
                        }
                    } );
                    return view;
                }
                catch ( InflateException e ) {
                }
                catch ( ClassNotFoundException e ) {
    
                }
            }
            return null;
        }
    }); }
    
          static void hackAndroid23(final String name,
            final android.util.AttributeSet attrs, final LayoutInflater f,
            final TextView[] view) {
        // mConstructorArgs[0] is only non-null during a running call to inflate()
        // so we make a call to inflate() and inside that call our dully XmlPullParser get's called
        // and inside that it will work to call "f.createView( name, null, attrs );"!
        try {
            f.inflate(new XmlPullParser() {
      @Override
      public int next() throws XmlPullParserException, IOException {
                    try {
                        view[0] = (TextView) f.createView( name, null, attrs );
                    } catch (InflateException e) {
                    } catch (ClassNotFoundException e) {
                    }
                    throw new XmlPullParserException("exit");
    }   
             }, null, false);
        } catch (InflateException e1) {
            // "exit" ignored
        }
    }
    

    (随意投票给这个答案;))我测试它在Android 2.3上工作,并仍然可以在早期版本上工作 . 如果在以后的Android版本中再次出现任何问题,您只需看到默认的菜单样式

  • 3

    如果你想设置一个任意颜色,这似乎对 androidx 很有效 . 在KitKat和Pie上测试过 . 把它放到你的 AppCompatActivity

    @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        if (name.equals("androidx.appcompat.view.menu.ListMenuItemView") &&
                parent.getParent() instanceof FrameLayout) {
                ((View) parent.getParent()).setBackgroundColor(yourFancyColor);
        }
        return super.onCreateView(parent, name, context, attrs);
    }
    

    这将设置 android.widget.PopupWindow$PopupBackgroundView 的颜色,正如您可能已经猜到的那样,它会绘制背景颜色 . 没有透支,你也可以使用半透明颜色 .

  • 19

    有一点需要注意的是,你们这些问题就像很多其他帖子一样过于复杂!您需要做的就是创建具有您需要的任何背景的可绘制选择器,并将它们设置为实际项目 . 我只花了两个小时尝试你的解决方案(本页所有建议),但没有一个工作 . 更不用说有大量错误会严重影响你在那些try / catch块中的性能 .

    无论如何这里是一个菜单xml文件:

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:id="@+id/m1"
              android:icon="@drawable/item1_selector"
              />
        <item android:id="@+id/m2"
              android:icon="@drawable/item2_selector"
              />
    </menu>
    

    现在在你的item1_selector中:

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true" android:drawable="@drawable/item_highlighted" />
        <item android:state_selected="true" android:drawable="@drawable/item_highlighted" />
        <item android:state_focused="true" android:drawable="@drawable/item_nonhighlighted" />
        <item android:drawable="@drawable/item_nonhighlighted" />
    </selector>
    

    下次你决定通过加拿大去超市试试谷歌 Map !

  • 4

    这就是我解决我的问题 . 我只是在样式中指定了背景颜色和文本颜色 . 即res> values> styles.xml文件 .

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:itemBackground">#ffffff</item>
        <item name="android:textColor">#000000</item>
    </style>
    
  • 8
    protected void setMenuBackground() {
        getLayoutInflater().setFactory(new Factory() {
            @Override
            public View onCreateView (String name, Context context, AttributeSet attrs) {
                if (name.equalsIgnoreCase("com.android.internal.view.menu.IconMenuItemView")) {
                    try {
                        // Ask our inflater to create the view
                        LayoutInflater f = getLayoutInflater();
                        final View view = f.createView(name, null, attrs);
                        // Kind of apply our own background
                        new Handler().post( new Runnable() {
                            public void run () {
                                view.setBackgroundResource(R.drawable.gray_gradient_background);
                            }
                        });
                        return view;
                    }
                    catch (InflateException e) {
                    }
                    catch (ClassNotFoundException e) {
                    }
                }
                return null;
            }
        });
    }
    

    这是XML文件

    gradient 
        android:startColor="#AFAFAF" 
        android:endColor="#000000"
        android:angle="270"
    shape
    
  • 12
    <style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:itemBackground">#000000</item>
    </style>
    

    这对我来说很好

  • 3

    刚刚遇到这个问题,在一个必须与Gingerbread兼容的应用程序上,仍然尽可能多地保留了支持Holo的设备的样式 .

    我找到了一个相对干净的解决方案,对我来说很好用 .

    在主题中,我使用9补丁可绘制背景来获得自定义背景颜色:

    <style name="Theme.Styled" parent="Theme.Sherlock">
       ...
       <item name="android:panelFullBackground">@drawable/menu_hardkey_panel</item>
    </style>
    

    我放弃了尝试设置文本颜色的样式,并使用Spannable在代码中为我的项目设置文本颜色:

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
       MenuInflater inflater = getSupportMenuInflater();
       inflater.inflate(R.menu.actions_main, menu);
    
       if (android.os.Build.VERSION.SDK_INT < 
            android.os.Build.VERSION_CODES.HONEYCOMB) {
    
            SpannableStringBuilder text = new SpannableStringBuilder();
            text.append(getString(R.string.action_text));
            text.setSpan(new ForegroundColorSpan(Color.WHITE), 
                    0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    
            MenuItem item1 = menu.findItem(R.id.action_item1);
            item1.setTitle(text);
       }
    
       return true;
    }
    
  • 3

    花了相当多的时间尝试所有选项后,我能够使用AppCompat v7更改溢出菜单背景的唯一方法是使用itemBackground属性:

    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        ...
        <item name="android:itemBackground">@color/overflow_background</item>
        ...
    </style>
    

    从API 4.2到5.0测试 .

  • 5
    /* 
         *The Options Menu (the one that pops up on pressing the menu button on the emulator) 
         * can be customized to change the background of the menu 
         *@primalpop  
       */ 
    
        package com.pop.menu;
    
        import android.app.Activity;
        import android.content.Context;
        import android.os.Bundle;
        import android.os.Handler;
        import android.util.AttributeSet;
        import android.util.Log;
        import android.view.InflateException;
        import android.view.LayoutInflater;
        import android.view.Menu;
        import android.view.MenuInflater;
        import android.view.View;
        import android.view.LayoutInflater.Factory;
    
        public class Options_Menu extends Activity {
    
            private static final String TAG = "DEBUG";
    
            /** Called when the activity is first created. */
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
    
            }
    
            /* Invoked when the menu button is pressed */
    
            @Override
            public boolean onCreateOptionsMenu(Menu menu) {
                // TODO Auto-generated method stub
                super.onCreateOptionsMenu(menu);
                MenuInflater inflater = new MenuInflater(getApplicationContext());
                inflater.inflate(R.menu.options_menu, menu);
                setMenuBackground();
                return true;
            }
    
            /*IconMenuItemView is the class that creates and controls the options menu 
             * which is derived from basic View class. So We can use a LayoutInflater 
             * object to create a view and apply the background.
             */
            protected void setMenuBackground(){
    
                Log.d(TAG, "Enterting setMenuBackGround");
                getLayoutInflater().setFactory( new Factory() {
    
                    @Override
                    public View onCreateView ( String name, Context context, AttributeSet attrs ) {
    
                        if ( name.equalsIgnoreCase( "com.android.internal.view.menu.IconMenuItemView" ) ) {
    
                            try { // Ask our inflater to create the view
                                LayoutInflater f = getLayoutInflater();
                                final View view = f.createView( name, null, attrs );
                                /* 
                                 * The background gets refreshed each time a new item is added the options menu. 
                                 * So each time Android applies the default background we need to set our own 
                                 * background. This is done using a thread giving the background change as runnable
                                 * object
                                 */
                                new Handler().post( new Runnable() {
                                    public void run () {
                                        view.setBackgroundResource( R.drawable.background);
                                    }
                                } );
                                return view;
                            }
                            catch ( InflateException e ) {}
                            catch ( ClassNotFoundException e ) {}
                        }
                        return null;
                    }
                });
            }
        }
    

相关问题