2010-05-31 7 views
76

Я пытаюсь изменить цвет по умолчанию для меню опций, которое является белым: мне нужен черный фон для каждого элемента меню опций.Как изменить цвет фона в меню параметров?

Я пробовал некоторые съемки как android: itemBackground = "# 000000" элемента элемента внутри элемента меню, но он не работал.

Как это сделать?

+0

https://developer.android.com/training/basics/actionbar/styling.html –

ответ

3
/* 
    *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; 
       } 
      }); 
     } 
    } 
+3

Пожалуйста, не делайте этого: name.equalsIgnoreCase («ком. android.internal.view.menu.IconMenuItemView " Как ясно указывает имя, это частная информация о реализации и, таким образом, может прерывать любое обновление платформы или устройство. – hackbod

+1

IconMenuItemView - это класс, который создает и управляет меню опций, которое полученный из базового класса View. Этот класс является исходным кодом android и присутствует с момента l east api версии 5. Я не вижу, чтобы это ломалось на любом обновлении платформы или устройстве. – primpap

+1

Вы не можете видеть это, потому что вы не можете видеть будущее. Даже если бы был способ убедиться, это плохая практика – HXCaine

18

Атрибут стиля для фона меню - android:panelFullBackground.

Несмотря на то, что говорит документация, он должен быть ресурсом (например, @android:color/black или @drawable/my_drawable), он сработает, если вы используете значение цвета напрямую.

Это также избавит вас от границ элементов, которые я не смог изменить или удалить с помощью решения primalpop.

Что касается цвета текста, я не нашел способа установить его через стили в 2.2, и я уверен, что все пробовал (так я обнаружил атрибут фона меню). Для этого вам нужно будет использовать решение primalpop.

+3

Где я должен установить это значение? Я не мог заставить его работать на Android 2.2. или 2.3 – Janusz

+1

@Janusz In Styles.xml. Это, вероятно, поможет: http://developer.android.com/guide/topics/resources/style-resource.html –

+0

Действительно благодарю вас, это решение –

3
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 
14

для Android 2.3 это может быть сделано с некоторыми очень тяжелым хакерства:

Основной причиной для проблем с Android 2.3 является то, что в LayoutInflater mConstructorArgs [0] = mКонтекст устанавливается только во время выполнения вызовов до

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, вы просто увидите стиль меню по умолчанию вместо

+0

Этот код работает только до версии 2.1 Этот код здесь кажется чтобы быть лучше: http://stackoverflow.com/questions/2944244/change-the-background-color-of-the-options-menu/8475357#8475357 –

+0

Привет, я использовал вашу функцию, но я получил следующую ошибку * * Ошибка раздувания класса com.android.internal.view.menu.IconMenuItemView ** и затем еще одно исключение ** Ошибка раздувания класса ** ... теперь что мне теперь делать ...? пожалуйста помогите. –

3

Спасибо Marcus! Он работает на 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 
    } 
} 
+0

hai Halo Ha спасибо за исправление. но теперь я не могу выбрать – Sando

+1

Все, что я получаю для этого: java.lang.IllegalStateException: фабрика уже была установлена ​​на этом LayoutInflater – Bostone

+0

, чтобы он работал с ActionBarSherlock и средой совместимости и избегал IllegalStateException, увидев этот трюк http: // stackoverflow. com/questions/13415284/java-lang-illegalstateexception-a-factory-has-already-was-set-on-this-layoutin/18606321 # 18606321 – avianey

8

Одна вещь, чтобы отметить, что вы, ребята, чрезмерно усложнять проблему так же, как много других сообщений! Все, что вам нужно сделать, это создать гибкие селектора с любыми фонами, которые вам нужны, и установить их в фактические элементы. Я просто потрачу два часа на то, чтобы пробовать ваши решения (все предлагаемые на этой странице), и никто из них не работал. Не говоря уже о том, что существует множество ошибок, которые существенно замедляют вашу производительность в тех блоках 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 Maps!

+0

Я полностью согласен. Зачем изобретать Android, когда он =) уже существует? – Fredrik

+0

Работает отлично. Создайте список слоев, подходящий для вашего значка и желаемого фона. Единственная проблема заключается в том, что я не знаю, могу ли я изменить цвет текста. Поэтому не каждый цвет фона работает – Janusz

+0

Ну, это не меняет фактический фон. Имеет ли это? – johan

49

Это явно проблема, с которой сталкиваются многие программисты и которой 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 реальные устройства). У меня нет никаких 3.X-планшетов для тестирования, но я буду публиковать любые необходимые изменения здесь, когда/если я это сделаю. Учитывая, что 3.X таблетки использовать Action Bars вместо опций меню, как описано здесь:

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

РЕЗЮМЕ SOLUTIONS SO FAR:

Несколько плакатов предложили взломать с помощью 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, поскольку они используют Action Bars вместо меню Options.

Наконец-то некоторые разработчики решили эту проблему, полностью подавив меню «Настройки 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; 
     } 
    }); 
} 

Спасибо за чтение и наслаждение!

+15

Единственное, что я получаю надежно, когда пытаюсь использовать это (и подобное решение), это 'java.lang.IllegalStateException: фабрика уже установлена ​​на этом LayoutInflater' – Bostone

+0

Работает для меня! Настолько здорово, что наконец-то есть решение! Протестировано на Gingerbread, Honeycomb и ICS –

+0

Протестировано в Samsung Galaxy Nexus (4.1.1) и работает! Хороший материал, Луис! –

12

Просто натолкнулся на эту проблему, в приложении, которое должно быть совместимо с пряниками и по-прежнему сохранять как можно больше стилей с помощью устройств с поддержкой 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; 
} 
+0

отлично подходит для моей проблемы с использованием темы ActionBarSherlock Light на устройстве Gingerbread! С этим я могу легко изменить фон меню опций на светло-серый и цвет текста на черный (значки уже черные, как в ActionBar! Спасибо! – evident

+0

Чистое и приятное решение. Прекрасно работает. Спасибо. +1 голос – Corbella

38

проведя значительное количество времени, пытаясь все варианты, единственный способ, которым я был в состоянии получить приложение, используя 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.

+0

Это должен быть принят, легко и просто. –

+1

Но это устраняет эффект пульсации:/В любом случае, чтобы вернуть его обратно? –

2
<style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar"> 
    <item name="android:itemBackground">#000000</item> 
</style> 

это работает отлично для меня

+0

Как изменить цвет заголовка –

Смежные вопросы