105

オプションメニューのデフォルトの色である白を変更しようとしています。オプションメニューのすべての項目に黒の背景が必要です。

menu要素内のitem要素でandroid:itemBackground = "#000000"のようないくつかの撮影を試しましたが、機能しませんでした。

どうすればこれを達成できますか?

4

13 に答える 13

77

すべてのオプションを試すのにかなりの時間を費やした後、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 までテスト済み。

于 2015-03-11T20:42:20.817 に答える
52

これは明らかに多くのプログラマーが抱えている問題であり、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#options-menu

このハックは、ほぼ確実に 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 インスタンスで LayoutInflater.inflate() への呼び出し内にいなくても、直接 LayoutInflater.getView() を呼び出すことができるということです。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 のビューを置き換える方法を理解する時間があれば (ここの詳細に悪魔がいると確信しています)、それは何も必要としない素晴らしい解決策になるかもしれません。文書化されていないハッキング。

ハック:

これがコードです。

このコードを使用するには、アクティビティ onCreate() またはアクティビティ onCreateOptionsMenu() から addOptionsMenuHackerInflaterFactory() を 1 回呼び出します。以降のオプション メニューの作成に影響を与えるデフォルト ファクトリを設定します。すでに作成されているオプション メニューには影響しません (以前のハックでは 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;
        }
    });
}

読んでくれてありがとう!

于 2011-12-12T13:51:41.247 に答える
20

メニュー背景のスタイル属性は ですandroid:panelFullBackground

ドキュメントに記載されていることにもかかわらず、リソース (@android:color/blackまたは など@drawable/my_drawable) である必要があります。色の値を直接使用するとクラッシュします。

これにより、primalpop のソリューションを使用して変更または削除できなかったアイテムの境界線も削除されます。

テキストの色については、2.2 でスタイルを介して設定する方法が見つかりませんでした。すべてを試したと確信しています (これが、メニューの背景属性を発見した方法です)。そのためには、primalpopのソリューションを使用する必要があります。

于 2011-03-05T18:40:31.090 に答える
13

Android 2.3 の場合、これは非常に重いハッキングで実行できます。

Android 2.3 の問題の根本的な原因は、LayoutInflater で mConstructorArgs[0] = mContext が呼び出しの実行中にのみ設定されることです。

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 の以降のバージョンで何か問題が発生した場合は、代わりにデフォルトのメニュー スタイルが表示されます。

于 2011-04-13T10:33:23.013 に答える
8

他の多くの投稿と同じように、問題を複雑にしすぎていることに注意してください。あなたがする必要があるのは、必要な背景で描画可能なセレクターを作成し、それらを実際のアイテムに設定することだけです。私はあなたの解決策(すべてこのページで提案されています)を試すのに2時間費やしましたが、どれもうまくいきませんでした. 言うまでもなく、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 マップを試してみてください。

于 2011-05-28T01:07:10.027 に答える
4
 <style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:itemBackground">#000000</item>
</style>

これは私にとってはうまくいきます

于 2016-04-05T08:48:29.027 に答える
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;
                }
            });
        }
    }
于 2010-05-31T14:50:24.300 に答える
3

ありがとうマーカス!いくつかの構文エラーを修正することにより、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
    }
}
于 2011-05-15T09:34:30.247 に答える
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
于 2011-04-07T11:18:55.250 に答える