Как изменить цвет фона меню настроек?
Я пытаюсь изменить цвет по умолчанию для меню параметров, который является белым: я хочу черный фон для каждого элемента в меню параметров.
Я попробовал несколько снимков, таких как android:itemBackground="#000000" на элементе item в элементе меню, но это не сработало.
Как я могу сделать это?
14 ответов
Потратив значительное количество времени на пробную работу со всеми опциями, я смог получить приложение с помощью 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 еще не предлагает удовлетворительного и поддерживаемого решения.
В сообщениях на эту тему встречается много противоречивых намерений и недоразумений, поэтому, пожалуйста, прочитайте весь ответ, прежде чем отвечать.
Ниже я привожу более "утонченную" и хорошо прокомментированную версию взлома из других ответов на этой странице, также включающую идеи из этих очень тесно связанных вопросов:
Изменить цвет фона меню Android
Как изменить цвет фона меню настроек?
Android: настроить меню приложения (например, цвет фона)
http://www.macadamian.com/blog/post/android_-_theming_the_unthemable/
Android MenuItem Переключатель
Можно ли сделать фон меню параметров Android непрозрачным?
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
этот взлом почти наверняка ничего не сделает (никакого вреда и ничего хорошего) на планшетах 3.X.
ПОСТАНОВКА ЗАДАЧИ (прочитайте это перед триггерным ответом с отрицательным комментарием):
Меню параметров имеет совершенно разные стили на разных устройствах. Чистый черный с белым текстом на некоторых, чистый белый с черным текстом на некоторых. Я и многие другие разработчики желаем контролировать цвет фона ячеек меню "Параметры", а также цвет текста меню "Параметры".
Некоторым разработчикам приложений нужно только установить цвет фона ячейки (а не цвет текста), и они могут сделать это более чистым способом, используя стиль android: panelFullBackground, описанный в другом ответе. Однако в настоящее время нет способа управлять цветом текста меню "Параметры" с помощью стилей, поэтому можно использовать этот метод только для изменения фона на другой цвет, который не заставит текст "исчезнуть".
Мы хотели бы сделать это с помощью документированного решения, ориентированного на будущее, но оно просто недоступно для Android <= 2.3. Таким образом, мы должны использовать решение, которое работает в текущих версиях и разработано, чтобы минимизировать вероятность сбоя / поломки в будущих версиях. Нам нужно решение, которое в случае сбоя изящно возвращается к поведению по умолчанию.
Существует множество законных причин, по которым может потребоваться контролировать внешний вид меню параметров (как правило, для соответствия визуальному стилю для остальной части приложения), поэтому я не буду останавливаться на этом.
По этому поводу опубликована ошибка в Google Android: добавьте свою поддержку, отметив эту ошибку (обратите внимание, что Google не рекомендует комментарии "мне тоже": достаточно только звезды):
http://code.google.com/p/android/issues/detail?id=4441
РЕЗЮМЕ РЕШЕНИЙ ТАК ДАЛЕКО:
Несколько авторов предложили взлом с участием LayoutInflater.Factory. Предлагаемый взлом работал для Android <= 2.2 и не удался для Android 2.3, потому что взлом сделал недокументированное предположение: что можно вызывать LayoutInflater.getView() напрямую, не находясь в настоящее время внутри вызова LayoutInflater.inflate() в том же экземпляре LayoutInflater. Новый код в Android 2.3 нарушил это предположение и привел к исключению NullPointerException.
Мой слегка доработанный хак ниже не опирается на это предположение.
Кроме того, хаки также полагаются на использование внутреннего недокументированного имени класса "com.android.internal.view.menu.IconMenuItemView" в качестве строки (не в качестве типа Java). Я не вижу способа избежать этого и все же достичь заявленной цели. Тем не менее, можно сделать взлом осторожным способом, который откатится, если в текущей системе не появится "com.android.internal.view.menu.IconMenuItemView".
Опять же, поймите, что это взлом, и я ни в коем случае не утверждаю, что это будет работать на всех платформах. Но мы, разработчики, не живем в фантастическом академическом мире, где все должно быть по книге: у нас есть проблема, которую нужно решить, и мы должны решить ее как можно лучше. Например, маловероятно, что "com.android.internal.view.menu.IconMenuItemView" будет существовать на планшетах 3.X, поскольку они используют панели действий вместо меню параметров.
Наконец, некоторые разработчики решили эту проблему, полностью подавив меню параметров Android и написав свой собственный класс меню (см. Некоторые ссылки выше). Я не пробовал это, но если у вас есть время, чтобы написать свой собственный вид и выяснить, как заменить вид Android (я уверен, что дьявол в деталях здесь), то это может быть хорошим решением, которое не требует каких-либо недокументированные взломы.
HACK:
Вот код
Чтобы использовать этот код, вызовите addOptionsMenuHackerInflaterFactory() ONCE из вашей активности onCreate() или из вашей активности onCreateOptionsMenu(). Он устанавливает фабрику по умолчанию, которая будет влиять на последующее создание любого меню параметров. Это не влияет на меню параметров, которые уже были созданы (предыдущие хаки использовали имя функции 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.
Вот так я и решил свою. Я только что указал цвет фона и цвет текста в стилях. т.е. res > values > styles.xml file.
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:itemBackground">#ffffff</item>
<item name="android:textColor">#000000</item>
</style>
Для 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.
Я нашел относительно чистое решение, которое работало хорошо для меня.
В теме я использую нарисованный фон из 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;
}
Стоит отметить, что вы, ребята, слишком усложняете проблему, как и многие другие посты! Все, что вам нужно сделать, это создать прорисовываемые селекторы с любым фоном, который вам нужен, и установить для них фактические элементы. Я просто потратил два часа, чтобы попробовать ваши решения (все предложенные на этой странице), и ни одно из них не сработало. Не говоря уже о том, что есть множество ошибок, которые существенно замедляют вашу производительность в тех блоках 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>
В следующий раз, когда вы решите пойти в супермаркет через Канаду, попробуйте карты Google!
Если вы хотите установить произвольный цвет, это, кажется, работает довольно хорошо для 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
, который, как вы уже догадались, рисует цвет фона. Там нет оверрейда, и вы можете использовать полупрозрачные цвета.
При использовании Material3 попробуйте следующее:
<item name="popupMenuBackground">@color/white</item>
<style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:itemBackground">#000000</item>
</style>
это прекрасно работает для меня
Спасибо Маркус! Он работает на 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
}
}
/*
*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;
}
});
}
}
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
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)
}