这显然是许多程序员都面临的问题,而 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 个真实设备)上测试了这个 hack。我还没有要测试的 3.X 平板电脑,但如果我这样做了,我会在此处发布任何需要的更改。鉴于 3.X 平板电脑使用操作栏而不是选项菜单,如下所述:
http://developer.android.com/guide/topics/ui/menus.html#options-menu
这种 hack 几乎肯定不会对 3.X 平板电脑造成任何影响(无害也无益)。
问题陈述(在触发-回复负面评论之前阅读此内容):
“选项”菜单在不同设备上的风格截然不同。纯黑,有些是白字,纯白,有些是黑字。我和许多其他开发人员希望控制选项菜单单元格的背景颜色以及选项菜单文本的颜色。
某些应用程序开发人员只需要设置单元格背景颜色(而不是文本颜色),他们可以使用另一个答案中描述的 android:panelFullBackground 样式以更简洁的方式执行此操作。但是,目前还没有办法用样式来控制选项菜单文本的颜色,所以只能用这种方法将背景更改为另一种不会使文本“消失”的颜色。
我们很乐意使用一个记录在案的、面向未来的解决方案来做到这一点,但从 Android
可能需要控制选项菜单的外观(通常是为了匹配应用程序其余部分的视觉风格)有很多正当理由,所以我不会详述。
发布了一个关于此问题的 Google Android 错误:请通过为该错误加星标来表示您的支持(注意 Google 不鼓励“我也是”cmets:只需一个星号就足够了):
http://code.google.com/p/android/issues/detail?id=4441
到目前为止的解决方案摘要:
有几位发帖人提出了涉及 LayoutInflater.Factory 的 hack。建议的 hack 对 Android
我在下面稍微改进的 hack 不依赖于这个假设。
此外,黑客还依赖于使用内部的、未记录的类名“com.android.internal.view.menu.IconMenuItemView”作为字符串(而不是 Java 类型)。我看不出有任何方法可以避免这种情况并仍然实现既定目标。但是,如果当前系统上没有出现“com.android.internal.view.menu.IconMenuItemView”,则可以谨慎地进行黑客攻击。
再次,请理解这是一个 hack,我绝不声称这将适用于所有平台。但是我们开发人员并不是生活在一个幻想的学术世界中,一切都必须按部就班:我们有一个问题要解决,我们必须尽我们所能解决它。例如,“com.android.internal.view.menu.IconMenuItemView”似乎不太可能出现在 3.X 平板电脑上,因为它们使用操作栏而不是选项菜单。
最后,一些开发人员通过完全禁止 Android 选项菜单并编写自己的菜单类解决了这个问题(参见上面的一些链接)。我还没有尝试过,但是如果您有时间编写自己的视图并弄清楚如何替换 Android 的视图(我相信这里的细节是魔鬼),那么它可能是一个不需要任何的不错的解决方案未记录的黑客攻击。
破解:
这是代码。
要使用此代码,请从您的活动 onCreate() 或活动 onCreateOptionsMenu() 调用 addOptionsMenuHackerInflaterFactory() 一次。它设置了一个默认工厂,它将影响任何选项菜单的后续创建。它不会影响已经创建的选项菜单(之前的 hack 使用了 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;
}
});
}
感谢阅读和享受!