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

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

我尝试过一些像android:itemBackground =“#000000”这样的菜单元素中的元素元素,但它不起作用。

我怎样才能做到这一点?

这显然是很多程序员所面临的问题,而且Google尚未提供令人满意的支持解决scheme。

在这个主题上有很多关于post的意图和误解,所以请在回答之前阅读完整的答案。

下面我在这个页面上join一个更加“精炼”和更好评论的黑客攻击版本,也包含这些非常密切相关的问题的想法:

更改android菜单的背景颜色

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

Android:自定义应用程序的菜单(例如背景颜色)

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

Android MenuItem切换button

是否有可能使Android选项菜单背景不透明?

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

将菜单背景设置为不透明

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

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

这个黑客几乎肯定会在3.X平板电脑上无所事事(没有伤害也不好)。

关于问题的陈述(在触发回答之前阅读并带有负面评论):

“选项”菜单在不同设备上的风格大不相同。 纯黑色的白色文字在一些纯白色的黑色文字上。 我和其他许多开发人员希望控制“选项”菜单单元格的背景颜色以及“选项”菜单文本的颜色

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

我们希望能够通过一个文档化的,面向未来的解决scheme来实现这一点,但是从Android <= 2.3不能使用。 所以我们必须使用一个能够在当前版本中运行的解决scheme,旨在最大限度地减less未来版本崩溃的可能性。 我们想要一个解决scheme,如果它失败了,就会优雅地返回到默认行为。

有很多合法的原因,为什么需要控制选项菜单的外观(通常为应用程序的其余部分匹配的视觉风格),所以我不会详谈。

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

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

迄今为止的解决scheme摘要:

几个海报build议涉及LayoutInflater.Factory的破解。 build议的hack对Android <2.2是有效的,对于Android 2.3是失败的,因为hack做了一个无法certificate的假设:可以直接调用LayoutInflater.getView()而不需要调用同一个LayoutInflater实例的LayoutInflater.inflate()。 Android 2.3中的新代码打破了这个假设,导致了NullPointerException。

下面我稍微精炼的黑客并不依赖于这个假设。

此外,黑客还依赖于使用内部的,未公开的类名“com.android.internal.view.menu.IconMenuItemView”作为string(而不是Javatypes)。 我看不出有什么办法可以避免这一点,并且仍然达到了既定目标。 但是,如果“com.android.internal.view.menu.IconMenuItemView”没有出现在当前系统上,那么就可以谨慎地进行破解。

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

最后,一些开发人员通过完全禁止Android选项菜单并编写自己的菜单类(参见上面的一些链接)解决了这个问题。 我没有尝试过,但如果你有时间写自己的视图,并找出如何取代Android的视图(我敢肯定恶魔在这里的细节),那么这可能是一个不错的解决scheme,不需要任何无证黑客。

HACK:

这是代码。

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

感谢阅读和享受!

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

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

testing从API 4.2到5.0。

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

尽pipe文档说什么,它需要是一个资源(例如@android:color/black@drawable/my_drawable ),它会崩溃,如果你直接使用颜色值。

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

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

对于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 } } 

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

刚刚碰到这个问题,在一个应用程序,必须与姜饼兼容,仍然保留尽可能多的霍洛启用设备的样式。

我find了一个相对干净的解决scheme,对我来说工作正常。

在主题中,我使用了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; } 

有一件事要注意,你们像其他许多post一样,过度复杂化了这个问题! 所有你需要做的是创build具有你需要的任何背景的可绘制select器,并将它们设置为实际项目。 我只花了两个小时尝试你的解决scheme(所有build议在这个页面上),他们都没有工作。 更何况,有大量的错误,实质上会减慢你的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> 

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

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

谢谢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 } } 
 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 
  <style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:itemBackground">#000000</item> </style> 

这对我来说工作得很好

Interesting Posts