我试图更改选项菜单的默认颜色为白色:我希望选项菜单上的每个项目都为黑色背景。
我已经尝试过在菜单元素中的item元素上进行类似android:itemBackground =“#000000”的拍摄,但是没有用。
我该怎么做?
我试图更改选项菜单的默认颜色为白色:我希望选项菜单上的每个项目都为黑色背景。
我已经尝试过在菜单元素中的item元素上进行类似android:itemBackground =“#000000”的拍摄,但是没有用。
我该怎么做?
Answers:
在花费了大量时间尝试所有选项之后,我能够使用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进行了测试。
显然,这是许多程序员都面临的问题,而Google尚未为之提供令人满意的,受支持的解决方案。
关于此主题的帖子周围有很多不同的意图和误解,因此请在回答之前阅读整个答案。
下面,我在此页面上的其他答案中提供了一个更“精炼”和注释良好的hack版本,还结合了与这些密切相关的问题的构想:
http://www.macadamian.com/blog/post/android_-_theming_the_unthemable/
http://www.codeproject.com/KB/android/AndroidMenusMyWay.aspx
我在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开始,该解决方案根本不可用。因此,我们必须使用在当前版本中可用的解决方案,该解决方案旨在最大程度地减少在将来的版本中崩溃/崩溃的机会。我们想要一个必须失败的解决方案,该解决方案应能够正常恢复到默认行为。
有很多合理的原因,为什么可能需要控制“选项”菜单的外观(通常与应用程序的其余部分匹配视觉样式),所以我不再赘述。
对此发布了一个Google Android错误:请为此错误加注星标,以增加支持(请注意Google不鼓励“我也是”评论:只需加星号即可):
http://code.google.com/p/android/issues/detail?id=4441
解决方案概要:
一些发布者建议涉及LayoutInflater.Factory的黑客。建议的hack在Android <= 2.2上有效,而在Android 2.3上失败,因为该hack做出了一个没有记录的假设:人们可以直接调用LayoutInflater.getView(),而无需当前在同一LayoutInflater实例上对LayoutInflater.inflate()的调用内。Android 2.3中的新代码打破了这一假设,并导致NullPointerException。
我下面略微完善的技巧并不依赖于此假设。
此外,黑客还依赖于使用内部未公开的类名称“ com.android.internal.view.menu.IconMenuItemView”作为字符串(而不是Java类型)。我看不出有什么办法可以避免这种情况,但仍然可以实现既定目标。但是,如果当前系统上未出现“ com.android.internal.view.menu.IconMenuItemView”,则可能会以谨慎的方式进行破解。
同样,请理解这是一种黑客行为,我绝对不声称此方法适用于所有平台。但是,我们的开发人员并没有生活在幻想的学术世界中,在这本书中,一切都必须靠书本:我们有一个要解决的问题,我们必须尽力解决。例如,在3.X平板电脑上似乎不太可能存在“ com.android.internal.view.menu.IconMenuItemView”,因为它们使用操作栏而不是选项菜单。
最后,一些开发人员通过完全取消显示“ Android选项菜单”并编写自己的菜单类来解决此问题(请参见上面的一些链接)。我还没有尝试过,但是如果您有时间编写自己的View并弄清楚如何替换Android的View(我敢肯定这里是细节部分),那么它可能是一个不错的解决方案,不需要任何操作无证黑客。
哈克:
这是代码。
要使用此代码,请从活动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;
}
});
}
感谢您的阅读和享受!
菜单背景的样式属性为android:panelFullBackground
。
尽管文档中有说明,但它必须是一种资源(例如@android:color/black
或@drawable/my_drawable
),如果直接使用颜色值,它将崩溃。
这也将摆脱我无法使用primalpop解决方案更改或删除的项目边框。
至于文本颜色,我还没有找到通过2.2中的样式进行设置的任何方法,并且我确定我已经尝试了所有方法(这就是我发现菜单背景属性的方式)。您将需要使用primalpop的解决方案。
对于Android 2.3,这可以通过一些非常繁重的黑客操作来完成:
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
}
}
我测试了它可以在Android 2.3上运行,并且仍然可以在早期版本上运行。如果在更高的Android版本中再次出现问题,您将直接看到默认的菜单样式
刚刚在必须与Gingerbread兼容且仍保留来自启用Holo的设备的大多数样式的App上也遇到了此问题。
我找到了一个相对干净的解决方案,对我来说效果不错。
在主题中,我使用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;
}
这就是我解决我的问题的方式。我只是在样式中指定了背景色和文本色。即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>
需要注意的一件事是,你们就像许多其他帖子一样,使问题变得更加复杂!您要做的就是创建具有所需背景的可绘制选择器,并将其设置为实际项目。我只花了两个小时来尝试您的解决方案(所有建议均在本页上建议),但没有一个起作用。更不用说有大量的错误从根本上降低了您拥有的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>
下次您决定通过加拿大去超市时,尝试使用谷歌地图!
<style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:itemBackground">#000000</item>
</style>
这对我来说很好
/*
*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;
}
});
}
}
谢谢马库斯!通过修复一些语法错误,它可以在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
如果您想设置任意颜色,这对于似乎效果很好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
,就像您可能已经猜到的那样,它绘制背景色。没有透支,也可以使用半透明的颜色。