Próbuję zmienić domyślny kolor menu opcji, który jest biały: chcę mieć czarne tło dla każdej pozycji w menu opcji.
Wypróbowałem kilka sesji, takich jak android: itemBackground = "# 000000" na elemencie item w elemencie menu, ale to nie zadziałało.
Jak mogę to osiągnąć?
android
user-interface
layout-inflater
feragusper
źródło
źródło
Odpowiedzi:
Po spędzeniu znacznej ilości czasu na wypróbowaniu wszystkich opcji, jedynym sposobem, w jaki mogłem uzyskać aplikację za pomocą AppCompat v7 do zmiany tła przepełnionego menu, było użycie atrybutu itemBackground:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> ... <item name="android:itemBackground">@color/overflow_background</item> ... </style>
Testowane od API 4.2 do 5.0.
źródło
Jest to wyraźnie problem, który ma wielu programistów i któremu Google jeszcze nie zapewniło satysfakcjonującego, obsługiwanego rozwiązania.
Wokół postów na ten temat krąży wiele krzyżujących się intencji i nieporozumień, dlatego przed udzieleniem odpowiedzi prosimy o przeczytanie całej odpowiedzi.
Poniżej zamieszczam bardziej „dopracowaną” i dobrze skomentowaną wersję hackowania z innych odpowiedzi na tej stronie, zawierającą również pomysły z tych bardzo ściśle powiązanych pytań:
Zmień kolor tła menu Androida
Jak zmienić kolor tła menu opcji?
Android: dostosuj menu aplikacji (np. Kolor tła)
http://www.macadamian.com/blog/post/android_-_theming_the_unthemable/
Przycisk przełącznika elementu menu Android
Czy można ustawić nieprzezroczyste tło menu opcji Androida?
http://www.codeproject.com/KB/android/AndroidMenusMyWay.aspx
Ustawianie nieprzezroczystego tła menu
Przetestowałem ten hack na 2.1 (symulator), 2.2 (2 prawdziwe urządzenia) i 2.3 (2 prawdziwe urządzenia). Nie mam jeszcze żadnych tabletów 3.X do przetestowania, ale opublikuję tutaj wszelkie potrzebne zmiany, kiedy / jeśli to zrobię. Biorąc pod uwagę, że tablety 3.X używają pasków akcji zamiast menu opcji, jak wyjaśniono tutaj:
http://developer.android.com/guide/topics/ui/menus.html#options-menu
ten hack prawie na pewno nic nie da (nic złego i nic dobrego) na tabletach 3.X.
OPIS PROBLEMU (przeczytaj to przed odpowiedzią wyzwalającą z negatywnym komentarzem):
Menu Opcje ma bardzo różne style na różnych urządzeniach. Czysta czerń z białym tekstem na niektórych, czysta biel z czarnym tekstem na niektórych. Ja i wielu innych programistów chcemy kontrolować kolor tła komórek menu Opcje, a także kolor tekstu menu Opcje .
Niektórzy programiści aplikacji muszą tylko ustawić kolor tła komórki (nie kolor tekstu) i mogą to zrobić w bardziej przejrzysty sposób, korzystając ze stylu android: panelFullBackground opisanego w innej odpowiedzi. Jednak obecnie nie ma możliwości kontrolowania koloru tekstu w menu Opcje za pomocą stylów, więc można użyć tej metody tylko do zmiany koloru tła na inny, który nie spowoduje „zniknięcia” tekstu.
Chcielibyśmy to zrobić za pomocą udokumentowanego, przyszłościowego rozwiązania, ale jedno z nich jest po prostu niedostępne w systemie Android <= 2.3. Musimy więc użyć rozwiązania, które działa w aktualnych wersjach i jest zaprojektowane tak, aby zminimalizować ryzyko awarii / zepsucia w przyszłych wersjach. Chcemy rozwiązania, które wdzięcznie zawiedzie i przywróci domyślne zachowanie, jeśli musi zawieść.
Istnieje wiele uzasadnionych powodów, dla których może być konieczne kontrolowanie wyglądu menu opcji (zazwyczaj w celu dopasowania stylu wizualnego do reszty aplikacji), więc nie będę się nad tym rozwodzić.
Jest opublikowany błąd Google Android na ten temat: dodaj swoje wsparcie, oznaczając ten błąd gwiazdką (pamiętaj, że Google odradza komentarze „ja też”: wystarczy gwiazdka):
http://code.google.com/p/android/issues/detail?id=4441
PODSUMOWANIE DOTYCHCZASOWYCH ROZWIĄZAŃ:
Kilka plakatów sugerowało włamanie do LayoutInflater.Factory. Sugerowany hack działał dla Androida <= 2.2 i nie powiódł się dla Androida 2.3, ponieważ hack przyjął nieudokumentowane założenie: można wywołać LayoutInflater.getView () bezpośrednio, bez bieżącego wywoływania LayoutInflater.inflate () w tej samej instancji LayoutInflater. Nowy kod w systemie Android 2.3 złamał to założenie i doprowadził do wyjątku NullPointerException.
Mój nieco dopracowany hack poniżej nie opiera się na tym założeniu.
Co więcej, ataki polegają również na użyciu wewnętrznej, nieudokumentowanej nazwy klasy „com.android.internal.view.menu.IconMenuItemView” jako ciągu znaków (a nie typu Java). Nie widzę sposobu, aby tego uniknąć i nadal osiągać wyznaczony cel. Możliwe jest jednak ostrożne włamanie, które może się cofnąć, jeśli „com.android.internal.view.menu.IconMenuItemView” nie pojawi się w bieżącym systemie.
Ponownie, zrozum, że to hack i w żadnym wypadku nie twierdzę, że będzie działać na wszystkich platformach. Ale my, programiści, nie żyjemy w fantastycznym świecie akademickim, w którym wszystko musi być zgodne z książką: mamy problem do rozwiązania i musimy go rozwiązać najlepiej, jak potrafimy. Na przykład wydaje się mało prawdopodobne, że „com.android.internal.view.menu.IconMenuItemView” będzie istnieć na tabletach 3.X, ponieważ używają one pasków akcji zamiast menu opcji.
Wreszcie, niektórzy programiści rozwiązali ten problem, całkowicie pomijając menu opcji Androida i pisząc własną klasę menu (zobacz niektóre z linków powyżej). Nie próbowałem tego, ale jeśli masz czas na napisanie własnego widoku i wymyślenie, jak zastąpić widok Androida (jestem pewien, że diabeł tkwi tutaj w szczegółach), może to być fajne rozwiązanie, które nie wymaga żadnego nieudokumentowane hacki.
WŁAMAĆ SIĘ:
Oto kod.
Aby użyć tego kodu, wywołaj addOptionsMenuHackerInflaterFactory () RAZ w swojej aktywności onCreate () lub swojej aktywności onCreateOptionsMenu (). Ustawia domyślną fabrykę, która wpłynie na późniejsze tworzenie dowolnego menu opcji. Nie ma to wpływu na menu opcji, które zostały już utworzone (poprzednie hacki używały nazwy funkcji setMenuBackground (), co jest bardzo mylące, ponieważ funkcja nie ustawia żadnych właściwości menu przed powrotem).
@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; } }); }
Dziękuję za przeczytanie i miłej zabawy!
źródło
Atrybut stylu tła menu to
android:panelFullBackground
.Pomimo tego, co mówi dokumentacja, musi to być zasób (np.
@android:color/black
Lub@drawable/my_drawable
), ulegnie awarii, jeśli bezpośrednio użyjesz wartości koloru.Pozwoli to również usunąć obramowania przedmiotów, których nie mogłem zmienić ani usunąć za pomocą rozwiązania Primalpop.
Jeśli chodzi o kolor tekstu, nie znalazłem sposobu, aby ustawić go za pomocą stylów w 2.2 i jestem pewien, że próbowałem wszystkiego (w ten sposób odkryłem atrybut tła menu). Musiałbyś użyć do tego rozwiązania Primalpop.
źródło
W przypadku Androida 2.3 można to zrobić za pomocą bardzo ciężkiego hakowania:
Główną przyczyną problemów z Androidem 2.3 jest to, że w LayoutInflater mConstructorArgs [0] = mContext jest ustawiane tylko podczas wykonywania wywołań
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 } }
Przetestowałem go, aby działał na Androidzie 2.3 i nadal działał na wcześniejszych wersjach. Jeśli coś znowu się zepsuje w późniejszych wersjach Androida, zamiast tego zobaczysz po prostu domyślny styl menu
źródło
Właśnie napotkałem ten problem w aplikacji, która musiała być kompatybilna z Gingerbread i nadal zachowywać jak najwięcej stylizacji z urządzeń obsługujących Holo.
Znalazłem stosunkowo czyste rozwiązanie, które działało dobrze.
W motywie używam tła z 9 łatkami do rysowania, aby uzyskać niestandardowy kolor tła:
<style name="Theme.Styled" parent="Theme.Sherlock"> ... <item name="android:panelFullBackground">@drawable/menu_hardkey_panel</item> </style>
Zrezygnowałem ze stylizacji koloru tekstu i po prostu użyłem Spannable, aby ustawić kolor tekstu dla mojego elementu w kodzie:
@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; }
źródło
Oto jak rozwiązałem swój. Właśnie określiłem kolor tła i kolor tekstu w stylach. czyli plik 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>
źródło
Należy zauważyć, że nadmiernie komplikujecie problem, tak jak wiele innych postów! Wszystko, co musisz zrobić, to utworzyć selektory do rysowania z dowolnym tłem i ustawić je na rzeczywiste elementy. Po prostu spędziłem dwie godziny próbując twoich rozwiązań (wszystkie sugerowane na tej stronie) i żadne z nich nie zadziałało. Nie wspominając o tym, że jest mnóstwo błędów, które zasadniczo spowalniają twoją wydajność w tych blokach try / catch, które masz.
W każdym razie tutaj jest plik xml menu:
<?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>
Teraz w twoim 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>
Następnym razem, gdy zdecydujesz się pójść do supermarketu przez Kanadę, wypróbuj mapy Google!
źródło
<style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:itemBackground">#000000</item> </style>
to działa dobrze dla mnie
źródło
/* *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; } }); } }
źródło
Dzięki Marcus! Działa płynnie w wersji 2.3, naprawiając niektóre błędy składniowe, oto poprawiony kod
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 } }
źródło
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; } }); }
to jest plik XML
gradient android:startColor="#AFAFAF" android:endColor="#000000" android:angle="270" shape
źródło
Jeśli chcesz ustawić dowolny kolor, wydaje się, że działa to raczej dobrze
androidx
. Testowane na KitKat i Pie. Umieść to w swoimAppCompatActivity
:@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); }
To ustawia kolor
android.widget.PopupWindow$PopupBackgroundView
, który, jak można się domyślić, rysuje kolor tła. Nie ma przerysowania i możesz również użyć półprzezroczystych kolorów.źródło