2010-05-31 116 views
76

我试图改变白色的选项菜单的默认颜色:我想在选项菜单上的每个项目的黑色背景。如何更改选项菜单的背景颜色?

我尝试了一些像android:itemBackground =“#000000”这样的菜单元素内的项目元素,但它没有奏效。

我该如何做到这一点?

+0

https://developer.android.com/training/basics/actionbar/styling.html – 2014-10-06 15:34:15

回答

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

请不要做这工作得很好。 android.internal.view.menu.IconMenuItemView” 正如其名称清楚地表明,这是使用私有的实现细节,因此可以在任何平台上更新或设备损坏。 – hackbod 2010-05-31 18:00:58

+1

IconMenuItemView是创建和控制选项菜单这是类派生自基本的View类,这个类来自android源代码,并且自l处开始出现东api版本5.我看不到任何平台更新或设备上的突破。 – primpap 2010-05-31 18:23:05

+1

你无法看到它,因为你看不到未来。即使有一种方法可以肯定,这是不好的做法 – HXCaine 2010-05-31 19:29:05

18

菜单背景的样式属性为android:panelFullBackground

尽管文档中提到了它,但它必须是一个资源(例如@android:color/black@drawable/my_drawable),但如果您直接使用颜色值,它将会崩溃。

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

至于文字颜色,我还没有找到任何方式来设置它通过2.2中的样式,我敢肯定,我已经尝试了一切(这是我如何发现菜单背景属性)。你需要使用primalpop的解决方案。

+3

我必须在哪里设置此值?我无法让它在Android 2.2上运行。或2.3 – Janusz 2011-06-28 10:05:36

+1

@Janusz在Styles.xml中。这可能会帮助:http://developer.android.com/guide/topics/resources/style-resource.html – 2011-06-30 21:30:03

+0

真的三江源,这是解决方案 – 2013-01-15 15:58:11

3
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 
14

对于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版本中再次出现任何问题,您只需看到 默认的菜单样式

+0

此代码仅运行到2.1版本 这儿,这似乎代码要更好:http://stackoverflow.com/questions/2944244/change-the-background-color-of-the-options-menu/8475357#8475357 – 2012-09-14 17:02:36

+0

嗨,我已经使用你的功能,但我有这个错误* *错误充气类com.android.internal.view.menu.IconMenuItemView **,然后多一个例外**错误充气类 ** ...现在我现在该怎么办......?请帮帮我。 – 2012-09-20 11:29:03

3

感谢Marcus!它适用于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

hai Halo Ha谢谢你的修复。但现在我无法选择 – Sando 2011-05-26 12:09:31

+1

所有我得到这个:java.lang.IllegalStateException:一个工厂已经设置在这个LayoutInflater – Bostone 2012-01-08 21:43:03

+0

使它与ActionBarSherlock和兼容性框架一起工作,并避免IllegalStateException看到这个技巧http:// stackoverflow。 COM /问题/ 13415284/JAVA琅IllegalStateException异常-A-工厂已经-已经被设置上,这-layoutin/18606321#18606321 – avianey 2013-09-04 05:55:58

8

有一点要注意,你们是过于复杂的问题,就像很多其他职位!你所需要做的就是创建具有你需要的任何背景的可绘制选择器,并将它们设置为实际项目。我只花了两个小时来尝试你的解决方案(所有的建议在这个页面上),并没有一个工作。更何况,有大量的错误,实质上会减慢你的try/catch块的性能。

反正这里是一个菜单的XML文件:在您的item1_selector

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

现在:

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

你决定去加拿大通过尝试谷歌地图超市下一次!

+0

我完全同意。当他=)时,为什么要重新创建Android?) – Fredrik 2011-05-28 01:24:24

+0

工程很棒。用您的图标和所需的背景构建可绘制的图层列表。唯一的问题是,我不知道我是否可以更改文字颜色。因此不是每个背景颜色都可以工作 – Janusz 2011-06-28 10:14:08

+0

嗯,这并不会改变实际的背景。可以? – johan 2011-09-26 13:17:19

49

这显然是很多程序员有一个问题,到谷歌还没有提供令人满意的,支持的解决方案。

有很多交叉的意图和误解左右浮动的帖子关于这个主题的,所以请回答前仔细阅读这整个的答案。

下面我包括来自其他答案此页面上的黑客更“精”和良好注释的版本,也是从这些合并的想法非常密切相关的问题:

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平板电脑没有(没有伤害,没有好)。

声明的问题(有负面的评价触发回答之前,先阅读):

选项菜单有不同的设备上完全不同的风格。纯黑色,部分纯白色,部分黑色文字。我和许多其他开发商希望控制选项菜单细胞的背景颜色以及选项菜单文本的颜色。

某些应用程序开发人员只需要设置单元格背景颜色(而不是文本颜色),并且可以使用Android的更清洁的方式做到这一点:在另一个答案描述panelFullBackground风格。但是,目前没有办法使用样式控制选项菜单文本颜色,因此只能使用此方法将背景更改为不会使文本“消失”的另一种颜色。

我们很乐意与一个记录,面向未来的解决方案要做到这一点,但一个是根本不能作为Android的< = 2.3。所以我们必须使用能够在当前版本中运行的解决方案,并且旨在最大限度地减少未来版本崩溃/崩溃的可能性。我们想要一个解决方案,如果它必须失败,才会优雅地恢复到默认行为。

为什么有一个可能需要控制的选项菜单的外观许多正当的理由(通常以匹配的视觉风格的应用程序的其余部分),所以我不会纠缠于这一点。

有一个谷歌Android漏洞发布了关于这一点,请通过主演这个bug添加你的支持(注意谷歌不鼓励“我也是”的评论:只是一个明星就够了):将

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

摘要目前的解决方案:

几个海报建议涉及LayoutInflater.Factory的破解。建议的黑客适用于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”没有出现在当前系统上,那么可以小心翼翼地进行破解。

再一次,明白这是一个黑客,绝不是我声称这将适用于所有平台。但是我们的开发人员并不是生活在一个梦幻般的学术世界里,书中的一切都必须依靠:我们有一个需要解决的问题,我们必须尽我们所能解决问题。例如,“3. com.android.internal.view.menu.IconMenuItemView”似乎不太可能存在于3.X平板电脑上,因为它们使用操作栏而不是选项菜单。最后,一些开发者通过完全禁止Android选项菜单并编写他们自己的菜单类(参见上面的一些链接)解决了这个问题。我还没有尝试过,但如果你有时间写自己的视图并找出如何取代Android的视图(我敢肯定魔鬼在这里的细节),那么它可能是一个很好的解决方案,不需要任何无证黑客。

HACK:

这是代码。

要使用此代码,请从您的活动onCreate()或您的活动onCreateOptionsMenu()中调用addOptionsMenuHackerInflaterFactory()ONCE。它设置了一个默认的工厂,将影响后续创建任何选项菜单。它不会影响已经创建的选项菜单(以前的黑客使用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; 
     } 
    }); 
} 

感谢您的阅读和享受!

+15

当我尝试使用这个(和类似的解决方案)时,我唯一可靠地得到的是'java.lang.IllegalStateException:这个LayoutInflater已经设置了一个工厂' – Bostone 2012-01-08 21:41:10

+0

适合我!非常棒,终于有了解决方案!测试姜饼,蜂窝和ICS – 2012-05-31 20:02:31

+0

在Samsung Galaxy Nexus(4.1.1)中测试并正常运行!好东西,路易斯! – 2012-09-14 17:01:59

12

刚刚遇到这个问题,在一个应用程序,必须与姜饼兼容,仍然保留尽可能多的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; 
} 
+0

对于在姜饼设备上使用ActionBarSherlock Light主题时的问题非常有用!有了这个,我可以很容易地将选项菜单背景更改为浅灰色和文本颜色黑色(图标已经是黑色的,就像在ActionBar中一样!谢谢! – evident 2013-02-11 23:59:37

+0

Clean and nice solution。Works perfect。Thanks!+1投票 – Corbella 2013-04-10 09:00:30

38

花费大量的时间去尝试所有选项后,只有这样,我可以使用程序兼容性V7更改溢出菜单背景用的是itemBackground属性得到一个应用程序:

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

从API 4.2测试5.0。 name.equalsIgnoreCase(“COM:

+0

This应该是被接受的答案,简单易行 – 2016-11-29 20:41:53

+1

但是这样可以消除连锁反应:/无论如何放回去? – 2017-01-15 20:28:30

2
<style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar"> 
    <item name="android:itemBackground">#000000</item> 
</style> 

这对我来说

+0

如何更改标题颜色 – 2016-09-06 06:27:31

相关问题