Android 如何更改选项菜单的背景颜色?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2944244/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to change the background color of the options menu?
提问by feragusper
I'm trying to change the default color for the options menu which is white: I want a black background for every item on the options menu.
我正在尝试更改白色选项菜单的默认颜色:我希望选项菜单上的每个项目都有黑色背景。
I've tried some shoots like android:itemBackground="#000000" on the item element within the menu element but it didn't work.
我已经在菜单元素中的 item 元素上尝试了一些像 android:itemBackground="#000000" 这样的拍摄,但它没有用。
How can I accomplish this?
我怎样才能做到这一点?
回答by TheIT
After spending a considerable amount of time trying all the options, the only way I was able to get an app using AppCompat v7 to change the overflow menu background was using the itemBackground attribute:
在花了大量时间尝试所有选项后,我能够使用 AppCompat v7 获取应用程序以更改溢出菜单背景的唯一方法是使用 itemBackground 属性:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
...
<item name="android:itemBackground">@color/overflow_background</item>
...
</style>
Tested from API 4.2 to 5.0.
从 API 4.2 到 5.0 测试。
回答by Louis Semprini
This is clearly a problem that a lot of programmers have and to which Google has yet to provide a satisfactory, supported solution.
这显然是许多程序员都遇到的问题,Google 尚未提供令人满意的、受支持的解决方案。
There are a lot of crossed intentions and misunderstandings floating around posts on this topic, so please read this whole answer before responding.
关于这个主题的帖子有很多相互交叉的意图和误解,所以请在回复之前阅读整个答案。
Below I include a more "refined" and well-commented version of the hack from other answers on this page, also incorporating ideas from these very closely related questions:
下面我在本页的其他答案中包含了一个更“精致”和评论良好的黑客版本,还结合了这些非常相关的问题的想法:
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/
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
http://www.codeproject.com/KB/android/AndroidMenusMyWay.aspx
Setting the menu background to be opaque
I tested this hack on 2.1 (simulator), 2.2 (2 real devices), and 2.3 (2 real devices). I don't have any 3.X tablets to test on yet but will post any needed changes here when/if I do. Given that 3.X tablets use Action Bars instead of Options Menus, as explained here:
我在 2.1(模拟器)、2.2(2 个真实设备)和 2.3(2 个真实设备)上测试了这个 hack。我还没有任何 3.X 平板电脑可供测试,但我会在此发布任何需要的更改。鉴于 3.X 平板电脑使用操作栏而不是选项菜单,如下所述:
http://developer.android.com/guide/topics/ui/menus.html#options-menu
http://developer.android.com/guide/topics/ui/menus.html#options-menu
this hack will almost certainly do nothing (no harm and no good) on 3.X tablets.
这个 hack 几乎肯定不会对 3.X 平板电脑产生任何影响(没有伤害也没有好处)。
STATEMENT OF THE PROBLEM (read this before trigger-replying with a negative comment):
问题陈述(在使用负面评论触发回复之前阅读此内容):
The Options menu has vastly different styles on different devices. Pure black with white text on some, pure white with black text on some. I and many other developers wish to control the background color of the Options menu cells as well as the color of the Options menu text.
选项菜单在不同设备上的风格截然不同。有的纯黑加白字,有的纯白加黑字。我和许多其他开发人员希望控制选项菜单单元格的背景颜色以及选项菜单文本的颜色。
Certain app developers only need to set the cell background color (not the text color), and they can do this in a cleaner manner using the android:panelFullBackground style described in another answer. However, there is currently no way to control the Options menu text color with styles, and so one can only use this method to change the background to another color that won't make the text "disappear."
某些应用程序开发人员只需要设置单元格背景颜色(而不是文本颜色),他们可以使用另一个答案中描述的 android:panelFullBackground 样式以更简洁的方式执行此操作。但是,目前没有办法通过样式来控制选项菜单文本颜色,因此只能使用此方法将背景更改为另一种不会使文本“消失”的颜色。
We would love to do this with a documented, future-proof solution, but one is simply not available as of Android <= 2.3. So we have to use a solution that works in current versions and is designed to minimize the chances of crashing/breaking in future versions. We want a solution that fails gracefully back to the default behavior if it has to fail.
我们很想用一个有记录的、面向未来的解决方案来做到这一点,但从 Android <= 2.3 开始,这个解决方案根本不可用。因此,我们必须使用适用于当前版本的解决方案,旨在最大限度地减少未来版本崩溃/破坏的可能性。我们想要一个解决方案,如果它必须失败,它可以优雅地返回到默认行为。
There are many legitimate reasons why one may need to control the look of Options menus (typically to match a visual style for the rest of the app) so I won't dwell on that.
人们可能需要控制“选项”菜单的外观(通常是为了匹配应用程序其余部分的视觉样式)的原因有很多,因此我不会详述。
There is a Google Android bug posted about this: please add your support by starring this bug (note Google discourages "me too" comments: just a star is enough):
有一个关于此问题的 Google Android 错误发布:请通过为此错误加星标来添加您的支持(注意 Google 不鼓励“我也是”评论:只需一颗星就足够了):
http://code.google.com/p/android/issues/detail?id=4441
http://code.google.com/p/android/issues/detail?id=4441
SUMMARY OF SOLUTIONS SO FAR:
到目前为止的解决方案摘要:
Several posters have suggested a hack involving LayoutInflater.Factory. The suggested hack worked for Android <= 2.2 and failed for Android 2.3 because the hack made an undocumented assumption: that one could call LayoutInflater.getView() directly without currently being inside a call to LayoutInflater.inflate() on the same LayoutInflater instance. New code in Android 2.3 broke this assumption and led to a NullPointerException.
一些海报提出了一个涉及 LayoutInflater.Factory 的黑客攻击。建议的 hack 适用于 Android <= 2.2 而在 Android 2.3 上失败,因为该 hack 做出了一个未记录的假设:可以直接调用 LayoutInflater.getView() 而当前不在同一 LayoutInflater 实例上对 LayoutInflater.inflate() 的调用中。Android 2.3 中的新代码打破了这一假设并导致了 NullPointerException。
My slightly refined hack below does not rely on this assumption.
下面我稍微精炼的 hack 不依赖于这个假设。
Furthermore, the hacks also rely on using an internal, undocumented class name "com.android.internal.view.menu.IconMenuItemView" as a string (not as a Java type). I do not see any way to avoid this and still accomplish the stated goal. However, it is possible to do the hack in a careful way that will fall back if "com.android.internal.view.menu.IconMenuItemView" does not appear on the current system.
此外,黑客还依赖于使用内部未记录的类名“com.android.internal.view.menu.IconMenuItemView”作为字符串(而不是 Java 类型)。我看不出有任何方法可以避免这种情况并且仍然可以实现既定目标。但是,如果“com.android.internal.view.menu.IconMenuItemView”未出现在当前系统上,则可以以谨慎的方式进行黑客攻击。
Again, understand that this is a hack and by no means am I claiming this will work on all platforms. But we developers are not living in a fantasy academic world where everything has to be by the book: we have a problem to solve and we have to solve it as best we can. For example, it seems unlikely that "com.android.internal.view.menu.IconMenuItemView" will exist on 3.X tablets since they use Action Bars instead of Options Menus.
同样,请理解这是一种黑客行为,我绝不声称这将适用于所有平台。但是我们开发人员并没有生活在一个幻想的学术世界里,在那里一切都必须按照书本进行:我们有一个问题要解决,我们必须尽我们所能来解决它。例如,“com.android.internal.view.menu.IconMenuItemView”似乎不太可能存在于 3.X 平板电脑上,因为它们使用操作栏而不是选项菜单。
Finally, some developers have solved this problem by totally suppressing the Android Options Menu and writing their own menu class (see some of the links above). I haven't tried this, but if you have time to write your own View and figure out how to replace Android's view (I'm sure the devil's in the details here) then it might be a nice solution that doesn't require any undocumented hacks.
最后,一些开发人员通过完全抑制 Android 选项菜单并编写自己的菜单类来解决这个问题(参见上面的一些链接)。我还没有尝试过这个,但是如果你有时间编写自己的视图并弄清楚如何替换 Android 的视图(我确信这里的细节是魔鬼)那么它可能是一个很好的解决方案,不需要任何无证黑客。
HACK:
黑客:
Here is the code.
这是代码。
To use this code, call addOptionsMenuHackerInflaterFactory() ONCE from your activity onCreate() or your activity onCreateOptionsMenu(). It sets a default factory that will affect subsequent creation of any Options Menu. It does not affect Options Menus that have already been created (the previous hacks used a function name of setMenuBackground(), which is very misleading since the function doesn't set any menu properties before it returns).
要使用此代码,请从您的活动 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;
}
});
}
Thanks for reading and enjoy!
感谢阅读和享受!
回答by Pilot_51
The style attribute for the menu background is android:panelFullBackground
.
菜单背景的样式属性是android:panelFullBackground
.
Despite what the documentation says, it needs to be a resource (e.g. @android:color/black
or @drawable/my_drawable
), it will crash if you use a color value directly.
不管文档怎么说,它需要是一个资源(例如@android:color/black
或@drawable/my_drawable
),如果您直接使用颜色值,它会崩溃。
This will also get rid of the item borders that I was unable to change or remove using primalpop's solution.
这也将摆脱我无法使用 primalpop 的解决方案更改或删除的项目边框。
As for the text color, I haven't found any way to set it through styles in 2.2 and I'm sure I've tried everything (which is how I discovered the menu background attribute). You would need to use primalpop's solution for that.
至于文本颜色,我还没有找到通过 2.2 中的样式设置它的任何方法,而且我确定我已经尝试了所有方法(这就是我发现菜单背景属性的方式)。为此,您需要使用 primalpop 的解决方案。
回答by Marcus Wolschon
For Android 2.3 this can be done with some very heavy hacking:
对于 Android 2.3,这可以通过一些非常繁重的黑客攻击来完成:
The root cause for the issues with Android 2.3 is that in LayoutInflater the mConstructorArgs[0] = mContext is only set during running calls to
Android 2.3 问题的根本原因是在 LayoutInflater 中 mConstructorArgs[0] = mContext 仅在运行调用期间设置
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
}
}
I tested it to work on Android 2.3 and to still work on earlier versions. If anything breaks again in later Android versions you'll simply see the default menu-style instead
我测试了它可以在 Android 2.3 上运行并且仍然可以在早期版本上运行。如果在以后的 Android 版本中再次出现任何问题,您只会看到默认的菜单样式
回答by Nicolai Buch-Andersen
Just ran into this issue too, on an App that had to be compatible with Gingerbread and still retain as much of the styling from Holo-enabled devices as possible.
刚刚也遇到了这个问题,在一个必须与 Gingerbread 兼容的应用程序上,并且仍然尽可能多地保留支持 Holo 的设备的样式。
I found a relatively clean solution, that worked OK for me.
我找到了一个相对干净的解决方案,对我来说效果很好。
In the theme I use a 9-patch drawable background to get a custom background color:
在主题中,我使用 9-patch 可绘制背景来获得自定义背景颜色:
<style name="Theme.Styled" parent="Theme.Sherlock">
...
<item name="android:panelFullBackground">@drawable/menu_hardkey_panel</item>
</style>
I gave up trying to style the text color, and just used a Spannable to set the text color for my item in code:
我放弃了尝试设置文本颜色的样式,只使用 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;
}
回答by Bukunmi
This is how i solved mine. I just specified the background color and text color in styles. ie res > values > styles.xml file.
这就是我如何解决我的。我只是在样式中指定了背景颜色和文本颜色。即 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>
回答by dropsOfJupiter
One thing to note that you guys are over-complicating the problem just like a lot of other posts! All you need to do is create drawable selectors with whatever backgrounds you need and set them to actual items. I just spend two hours trying your solutions (all suggested on this page) and none of them worked. Not to mention that there are tons of errors that essentially slow your performance in those try/catch blocks you have.
需要注意的一件事是,你们就像很多其他帖子一样把问题复杂化了!您需要做的就是创建具有您需要的任何背景的可绘制选择器,并将它们设置为实际项目。我只花了两个小时尝试您的解决方案(所有建议都在此页面上),但都没有奏效。更不用说在那些 try/catch 块中,有大量错误实质上会降低您的性能。
Anyways here is a menu xml file:
无论如何,这是一个菜单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>
Now in your item1_selector:
现在在您的 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>
Next time you decide to go to the supermarket through Canada try google maps!
下次您决定通过加拿大去超市时,请尝试使用谷歌地图!
回答by Bincy Baby
<style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:itemBackground">#000000</item>
</style>
this works fine for me
这对我来说很好用
回答by Halo Ha
Thanks Marcus! It works on 2.3 smoothly by fixing some syntax errors, here's the fixed code
谢谢马库斯!通过修复一些语法错误,它可以在 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
}
}
回答by Android
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;
}
});
}
this is XML file
这是 XML 文件
gradient
android:startColor="#AFAFAF"
android:endColor="#000000"
android:angle="270"
shape