web-dev-qa-db-ja.com

オプションメニューの背景色を変更する方法は?

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

Menu要素内のitem要素でAndroid:itemBackground = "#000000"のような撮影を試しましたが、うまくいきませんでした。

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

93
feragusper

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

60
TheIT

これは明らかに多くのプログラマーが抱えている問題であり、Googleがまだ満足できるサポートされたソリューションを提供していない問題です。

このトピックに関する投稿には、さまざまな意図や誤解があふれているため、回答する前にこの回答全体をお読みください。

以下に、このページの他の回答からのより洗練された、コメントの充実したバージョンのハックを含めます。また、これらの非常に密接に関連する質問からのアイデアも取り入れています。

Androidメニューの背景色を変更する

オプションメニューの背景色を変更するには?

Android:アプリケーションのメニューをカスタマイズする(背景色など)

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

Android MenuItem Toggle Button

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

このハックは、ほぼ確実に3.Xタブレットでは何もしません(害も役に立たない)。

問題の説明(否定的なコメントでトリガー応答する前にこれを読んでください):

[オプション]メニューには、デバイスごとに大幅に異なるスタイルがあります。真っ黒な部分に白い文字、真っ白な部分に黒い文字があります。私と他の多くの開発者は、オプションメニューセルの背景色を制御したい オプションメニューテキストの色と同様に

特定のアプリ開発者は、セルの背景色(テキストの色ではなく)を設定するだけでよく、別の回答で説明されているAndroid:panelFullBackgroundスタイルを使用して、よりきれいに設定できます。ただし、現在、スタイルで[オプション]メニューのテキストの色を制御する方法はないため、このメソッドを使用して、テキストを「表示」しない他の色に変更することしかできません。

文書化された将来性のあるソリューションを使用してこれを実行したいと考えていますが、Android <= 2.3以降は使用できません。そのため、現在のバージョンで機能し、将来のバージョンでクラッシュ/破損する可能性を最小限に抑えるように設計されたソリューションを使用する必要があります。失敗する必要がある場合は、デフォルトの動作に正常に戻るソリューションが必要です。

オプションメニューの外観を制御する必要がある(多くの場合、アプリの他の部分の視覚的なスタイルに合わせるために)正当な理由がたくさんあるので、それについては詳しく説明しません。

これについてGoogle Androidバグが投稿されています。このバグにスターを付けてサポートを追加してください(Googleは「私も」コメントを推奨していません。スターだけで十分です)。

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

ソリューションの要約SO FAR:

いくつかのポスターは、LayoutInflater.Factoryに関連するハックを提案しています。提案されたハックはAndroid <= 2.2で機能し、Android 2.3で失敗しました。ハックは文書化されていない仮定を行ったためです:現在呼び出し中になくてもLayoutInflater.getView()を直接呼び出すことができます同じLayoutInflaterインスタンスのLayoutInflater.inflate()に。 Android 2.3の新しいコードはこの仮定を破り、NullPointerExceptionを引き起こしました。

以下の少し洗練されたハックは、この仮定に依存していません。

さらに、ハッキングは、文書化されていない内部クラス名「com.Android.internal.view.menu.IconMenuItemView」を文字列として使用することにも依存しています(Javaタイプとしてではありません)。これを回避する方法は見当たりませんが、それでも目標を達成できます。ただし、「com.Android.internal.view.menu.IconMenuItemView」が現在のシステムに表示されない場合、フォールバックする慎重な方法でハックを行うことは可能です。

繰り返しますが、これはハッキングであり、すべてのプラットフォームで機能すると主張しているわけではありません。しかし、私たちの開発者は、すべてが本である必要のある空想的なアカデミックな世界に住んでいません。解決すべき問題があり、できる限り解決しなければなりません。たとえば、3.Xタブレットでは「com.Android.internal.view.menu.IconMenuItemView」がオプションメニューではなくアクションバーを使用するため、存在する可能性は低いようです。

最後に、一部の開発者は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;
        }
    });
}

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

50
Louis Semprini

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

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

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

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

20
Pilot_51

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

14
Marcus Wolschon

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

これが私の解決方法です。スタイルで背景色とテキスト色を指定しました。すなわち、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>
11
Bukunmi

他の多くの投稿と同様に、皆さんが問題を複雑にしすぎていることに注意してください。必要なのは、必要な背景を使用して描画可能なセレクターを作成し、実際のアイテムに設定することだけです。私はあなたの解決策を試すのに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>

次回カナダを経由してスーパーに行くことにしたときは、グーグルマップを試してみてください!

9
dropsOfJupiter
 <style name="AppThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="Android:itemBackground">#000000</item>
</style>

これは私のためにうまくいきます

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

ありがとうマーカス! 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
    }
}
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;
        }
    });
}

これはXMLファイルです

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

任意の色を設定したい場合、これは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の色が設定されます。これは、ご想像のとおり、背景色を描画します。オーバードローはなく、半透明の色も使用できます。

1
squirrel