如何更改选项菜单的背景颜色?
Posted
技术标签:
【中文标题】如何更改选项菜单的背景颜色?【英文标题】:How to change the background color of the options menu? 【发布时间】:2011-02-26 00:27:32 【问题描述】:我正在尝试更改选项菜单的默认颜色为白色:我希望选项菜单上的每个项目都有黑色背景。
我在菜单元素中的 item 元素上尝试了一些类似 android:itemBackground="#000000" 的拍摄,但没有成功。
我怎样才能做到这一点?
【问题讨论】:
developer.android.com/training/basics/actionbar/styling.html 最好的方法在这里 ***.com/questions/3519277/… 【参考方案1】:在花费大量时间尝试所有选项之后,我能够使用 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。
【讨论】:
这应该是公认的答案,简单易行。 但这消除了连锁反应:/ 无论如何要放回去? 我想更改整个窗口背景,而不是单独的项目背景,例如,如果您设置这样的背景(带边框),它将为每个项目添加边框... 菜单文字颜色怎么样? @DavidVelasquez 要保持涟漪效应,请使用this answer。【参考方案2】:这显然是许多程序员都面临的问题,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;
);
感谢阅读和享受!
【讨论】:
我在尝试使用这个(和类似的解决方案)时唯一可靠的是`java.lang.IllegalStateException: A factory has been set on this LayoutInflater` 为我工作!太好了,终于有了解决方案!在 Gingerbread、Honeycomb 和 ICS 上测试 在三星 Galaxy Nexus (4.1.1) 中测试并正常工作!好东西,路易斯! 适用于 Galaxy Nexus 7 (4.1.1),但在首次隐藏后,每次后续调用菜单时,文本颜色都会恢复。 我也得到了 IllegalStateException。看起来该 hack 与我正在使用的 ActionBarSherlock 不兼容。【参考方案3】:菜单背景的样式属性为android:panelFullBackground
。
不管文档怎么说,它必须是资源(例如@android:color/black
或@drawable/my_drawable
),如果直接使用颜色值,它会崩溃。
这也将消除我无法使用 primalpop 的解决方案更改或删除的项目边框。
至于文本颜色,我还没有找到通过 2.2 中的样式设置它的任何方法,我确信我已经尝试了所有方法(这就是我发现菜单背景属性的方法)。为此,您需要使用 primalpop 的解决方案。
【讨论】:
我必须在哪里设置这个值?我无法让它在 Android 2.2 上运行。或 2.3 @Janusz 在 Styles.xml 中。这可能会有所帮助:developer.android.com/guide/topics/resources/… 不起作用,如果你能说明它应该去哪里,那就太好了,除了为我的菜单项设置另一种样式来属性之外,到处都尝试过.....【参考方案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>
【讨论】:
textcolor 会随处改变 它只是改变了项目的背景颜色,并且菜单选项布局有顶部和底部填充,它没有帮助【参考方案5】:刚刚在一个必须与 Gingerbread 兼容的应用上也遇到了这个问题,并且仍然尽可能多地保留支持 Holo 的设备的样式。
我找到了一个相对干净的解决方案,对我来说效果不错。
在主题中,我使用 9-patch 可绘制背景来获得自定义背景颜色:
<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;
【讨论】:
非常适合我在 Gingerbread 设备上使用 ActionBarSherlock Light 主题的问题!有了这个,我可以轻松地将选项菜单背景更改为浅灰色,将文本颜色更改为黑色(图标已经是黑色的,就像在 ActionBar 中一样!谢谢!【参考方案6】:对于 Android 2.3,这可以通过一些非常严重的黑客攻击来完成:
Android 2.3 出现问题的根本原因在于 布局充气机 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 版本中再次出现任何问题,您只会看到 改为默认菜单样式
【讨论】:
这段代码只能运行到2.1版本这里的代码似乎更好:***.com/questions/2944244/… 嗨,我已经使用了你的函数,但是我得到了以下错误 Error inflating class com.android.internal.view.menu.IconMenuItemView 然后又出现了一个异常 膨胀类有一点需要注意,就像许多其他帖子一样,你们将问题过于复杂化了!您需要做的就是创建具有您需要的任何背景的可绘制选择器,并将它们设置为实际项目。我只花了两个小时尝试您的解决方案(所有建议都在此页面上),但都没有奏效。更不用说在你拥有的那些 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>
下次你决定通过加拿大去超市时,试试谷歌地图!
【讨论】:
我完全同意。当他 =) 已经存在时,为什么要重新发明 Android? 效果很好。使用您的图标和所需的背景构建一个可绘制的图层列表。唯一的问题是我不知道是否可以更改文本颜色。因此,并非每种背景颜色都有效 漂亮、优雅,完全解决不了问题。 如果我没记错的话,这只是改变了图标的背景,而不是菜单项本身,它仍然是白色的。 这不是问题的答案。这是完全不同的想法。【参考方案8】: <style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:itemBackground">#000000</item>
</style>
这对我来说很好用
【讨论】:
如何改变标题颜色【参考方案9】:如果您想设置任意颜色,这似乎对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
的颜色,正如您可能已经猜到的那样,它会绘制背景颜色。没有过度绘制,您也可以使用半透明颜色。
【讨论】:
如果你想保持圆角的外观,使用backgroundTintList
而不是backgroundColor
可能会更好。【参考方案10】:
/*
*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;
);
【讨论】:
请不要这样做: name.equalsIgnoreCase( "com.android.internal.view.menu.IconMenuItemView" 顾名思义,这是使用私有实现细节,因此可以中断任何平台更新或设备。 IconMenuItemView 是创建和控制从基本视图类派生的选项菜单的类。此类来自 android 源代码,并且至少从 api 版本 5 开始就存在。我看不到它在任何平台更新或设备上都被破坏。 你看不到它,因为你看不到未来。即使有办法确定,也是不好的做法 谢谢,这在紧要关头很有用。但是,在特殊情况下不起作用,例如在 onCreateOptionsMenu 中创建的项目,但在 onPrepareOptionsMenu 中禁用,但后来又启用了。【参考方案11】:谢谢马库斯!通过修复一些语法错误,它可以在 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
【讨论】:
我得到的都是:java.lang.IllegalStateException: 已经在这个 LayoutInflater 上设置了工厂 使其与 ActionBarSherlock 和兼容性框架一起工作并避免 IllegalStateException 看到这个技巧***.com/questions/13415284/…【参考方案12】: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
【讨论】:
【参考方案13】:Kotlin Androidx
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View?
if (parent?.parent is FrameLayout)
(parent?.parent as View).setBackgroundColor(Color.parseColor("#33B5E5"))
return super.onCreateView(parent, name, context!!, attrs)
【讨论】:
以上是关于如何更改选项菜单的背景颜色?的主要内容,如果未能解决你的问题,请参考以下文章