web-dev-qa-db-fra.com

Comment changer la couleur de fond du menu d'options?

J'essaie de changer la couleur par défaut pour le menu d'options qui est blanc: je veux un fond noir pour chaque élément du menu d'options.

J'ai essayé des shootings comme Android: itemBackground = "# 000000" sur l'élément item dans l'élément de menu mais cela n'a pas fonctionné.

Comment puis-je accomplir cela?

85
feragusper

Après avoir passé beaucoup de temps à essayer toutes les options, la seule façon d'obtenir une application utilisant AppCompat v7 pour modifier l'arrière-plan du menu de dépassement de capacité était d'utiliser l'attribut itemBackground:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    ...
    <item name="Android:itemBackground">@color/overflow_background</item>
    ...
</style>

Testé d'API 4.2 à 5.0.

53
TheIT

Il s'agit clairement d'un problème rencontré par de nombreux programmeurs et pour lequel Google n'a pas encore fourni de solution satisfaisante et prise en charge.

Il y a beaucoup d'intentions croisées et de malentendus qui circulent autour des publications sur ce sujet. Veuillez donc lire cette réponse au complet avant de répondre.

Ci-dessous, je propose une version plus "raffinée" et bien commentée du hack provenant d’autres réponses données sur cette page, qui intègre également des idées tirées de ces questions très étroitement liées:

Changer la couleur de fond du menu Android

Comment changer la couleur de fond du menu d'options?

Android: personnaliser le menu de l'application (par exemple, la couleur de fond)

http://www.macadamian.com/blog/post/Android_-_theming_the_unthemable/

Android MenuItem Toggle Button

Est-il possible de rendre le fond du menu des options Android non translucide?

http://www.codeproject.com/KB/Android/AndroidMenusMyWay.aspx

Réglage de l'arrière-plan du menu sur opaque

J'ai testé ce piratage sur 2.1 (simulateur), 2.2 (2 appareils réels) et 2.3 (2 appareils réels). Je n'ai pas encore de comprimés 3.X à tester, mais je publierai les modifications nécessaires ici/si je le fais. Étant donné que les tablettes 3.X utilisent les barres d’action au lieu des menus Options, comme expliqué ici:

http://developer.Android.com/guide/topics/ui/menus.html#options-menu

ce bidouillage ne fera presque certainement rien (pas de mal et pas de bien) sur les tablettes 3.X.

DÉCLARATION DU PROBLÈME (à lire avant de répondre par un commentaire négatif au déclencheur): 

Le menu Options contient des styles très différents selon les appareils. Noir pur avec du texte blanc sur certains, blanc pur avec du texte noir sur certains. De nombreux autres développeurs et moi-même souhaitons contrôler la couleur de fond des cellules du menu Options. ainsi que la couleur du texte du menu Options.

Certains développeurs d'applications doivent uniquement définir la couleur d'arrière-plan de la cellule (et non la couleur du texte). Ils peuvent le faire de manière plus propre à l'aide du style Android: panelFullBackground décrit dans une autre réponse. Cependant, il n’existe actuellement aucun moyen de contrôler la couleur du texte du menu Options avec des styles. Par conséquent, vous ne pouvez utiliser cette méthode que pour modifier l’arrière-plan en une autre couleur qui ne fera pas disparaître le texte.

Nous aimerions pouvoir le faire avec une solution documentée et évolutive, mais celle-ci n’est tout simplement pas disponible à partir d’Android <= 2.3. Nous devons donc utiliser une solution qui fonctionne dans les versions actuelles et qui est conçue pour minimiser les risques de crash/rupture dans les versions futures. Nous voulons une solution qui échoue gracieusement au comportement par défaut si elle doit échouer.

Il existe de nombreuses raisons légitimes pour lesquelles il peut être nécessaire de contrôler l'aspect des menus Options (généralement pour correspondre à un style visuel pour le reste de l'application), afin que je ne m'attarde pas sur ce point.

Un bogue Google Android a été publié à ce sujet: veuillez ajouter votre soutien en le mettant en vedette (remarque: Google décourage les commentaires de "Moi aussi": il suffit d'une étoile):

http://code.google.com/p/Android/issues/detail?id=4441

SOMMAIRE DES SOLUTIONS SO

Plusieurs affiches ont suggéré un hack impliquant LayoutInflater.Factory. Le piratage suggéré a fonctionné pour Android <= 2.2 et a échoué pour Android 2.3, car il a formulé une hypothèse non documentée selon laquelle on pourrait appeler LayoutInflater.getView () directement sans être actuellement dans un appel à LayoutInflater.inflate () sur la même instance LayoutInflater. Le nouveau code dans Android 2.3 a cassé cette hypothèse et a conduit à une exception NullPointerException.

Mon hack légèrement raffiné ci-dessous ne repose pas sur cette hypothèse.

De plus, les hacks utilisent également un nom de classe interne non documenté, "com.Android.internal.view.menu.IconMenuItemView" en tant que chaîne (et non en tant que type Java). Je ne vois aucun moyen d’éviter cela et d’atteindre le but fixé. Cependant, il est possible de procéder au piratage de manière prudente si le message "com.Android.internal.view.menu.IconMenuItemView" n'apparaît pas sur le système actuel.

Encore une fois, comprenez que c’est un bidouillage et en aucun cas je prétends que cela fonctionnera sur toutes les plateformes. Mais nous, les développeurs, ne vivons pas dans un monde académique fantastique où tout doit être écrit comme il se doit: nous avons un problème à résoudre et nous devons le résoudre du mieux que nous pouvons. Par exemple, il semble peu probable que "com.Android.internal.view.menu.IconMenuItemView" existe sur les tablettes 3.X car elles utilisent des barres d’action au lieu des menus Options.

Enfin, certains développeurs ont résolu ce problème en supprimant totalement le menu Options Android et en écrivant leur propre classe de menu (voir certains des liens ci-dessus). Je n'ai pas essayé cela, mais si vous avez le temps d'écrire votre propre View et de trouver une solution pour remplacer celle d'Android (je suis sûr que le diable est dans les détails ici), il s'agira peut-être d'une solution intéressante ne nécessitant aucune solution. hacks non documentés.

PIRATER:

Voici le code.Pour utiliser ce code, appelez addOptionsMenuHackerInflaterFactory () de votre activité onCreate () ou de votre activité onCreateOptionsMenu (). Il définit une usine par défaut qui affectera la création ultérieure de tout menu Options. Cela n'affecte pas les menus d'options déjà créés (les hacks précédents utilisaient le nom de fonction setMenuBackground (), ce qui est très trompeur, car la fonction ne définit aucune propriété de menu avant son retour).

@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; } }); }

Thanks for reading and enjoy!

49
Louis Semprini

L'attribut de style pour l'arrière-plan du menu est Android:panelFullBackground.

Malgré ce que dit la documentation, il doit s'agir d'une ressource (par exemple, @Android:color/black ou @drawable/my_drawable), elle plantera si vous utilisez directement une valeur de couleur.

Cela permettra également de supprimer les bordures d'éléments que je n'ai pas pu modifier ni supprimer en utilisant la solution de primalpop.

En ce qui concerne la couleur du texte, je n’ai trouvé aucun moyen de le définir au moyen de styles dans la version 2.2 et je suis certain que j’ai tout essayé (c’est ainsi que j’ai découvert l’attribut background du menu). Vous auriez besoin d'utiliser la solution de primalpop pour cela.

20
Pilot_51

Pour Android 2.3, cela peut se faire avec un piratage très intensif:

La cause principale des problèmes avec Android 2.3 est que, dans LayoutInflater Le mConstructorArgs [0] = mContext est uniquement défini pendant les appels en cours à

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
    }
}

(N'hésitez pas à voter pour cette réponse;)) Je l'ai testée pour fonctionner sur Android 2.3 et sur les versions précédentes . Si quelque chose se brise à nouveau dans les versions ultérieures d'Android, vous verrez simplement le menu par défaut à la place

14
Marcus Wolschon

J'ai juste rencontré ce problème également, sur une application qui devait être compatible avec Gingerbread et conserver le plus de style possible sur les appareils compatibles Holo.

J'ai trouvé une solution relativement propre, qui a bien fonctionné pour moi.

Dans le thème, j'utilise un arrière-plan dessinable de 9 patchs pour obtenir une couleur d'arrière-plan personnalisée:

<style name="Theme.Styled" parent="Theme.Sherlock">
   ...
   <item name="Android:panelFullBackground">@drawable/menu_hardkey_panel</item>
</style>

J'ai arrêté d'essayer de styliser la couleur du texte et j'ai simplement utilisé Spannable pour définir la couleur du texte de mon article dans le code:

@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;
}
13

Une chose à noter que vous avez compliqué le problème à l’instar de nombreux autres posts! Tout ce que vous avez à faire est de créer des sélecteurs pouvant être dessinés avec tous les arrière-plans dont vous avez besoin et de les définir comme éléments réels. Je viens de passer deux heures à essayer vos solutions (toutes suggérées sur cette page) et aucune d’entre elles n’a fonctionné. Sans parler du fait qu'il existe des tonnes d'erreurs qui ralentissent vos performances dans les blocs try/catch que vous avez.

Quoi qu'il en soit, voici un fichier XML de 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>

Maintenant dans votre 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>

La prochaine fois que vous déciderez d'aller au supermarché au Canada, essayez Google Maps!

8
dropsOfJupiter

Voici comment j'ai résolu le mien. Je viens de spécifier la couleur d'arrière-plan et la couleur du texte Dans les styles. c'est-à-dire que 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>
7
Bukunmi
 <style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="Android:itemBackground">#000000</item>
</style>

cela fonctionne bien pour moi

4
Bincy Baby
    /* 
     *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
Primal Pappachan

Merci Marcus! Cela fonctionne sur la version 2.3 en corrigeant quelques erreurs de syntaxe, voici le code

    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
    }
}
3
Halo Ha
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;
        }
    });
}

c'est un fichier XML 

gradient 
    Android:startColor="#AFAFAF" 
    Android:endColor="#000000"
    Android:angle="270"
shape
3
Android

Si vous souhaitez définir une couleur arbitraire, cela semble plutôt bien fonctionner pour androidx. Testé sur KitKat et Pie. Mettez ceci dans votre 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);
}

Ceci définit la couleur de Android.widget.PopupWindow$PopupBackgroundView qui, comme vous l’auriez peut-être deviné, dessine la couleur d’arrière-plan. Il n'y a pas de sur-dessin et vous pouvez également utiliser des couleurs semi-transparentes.

0
squirrel