android layout_inflater_service 如何更改選項菜單的背景顏色?



layout_inflater_service (11)

剛剛遇到了這個問題,在一個應用程序必須與姜餅兼容,仍然保留盡可能多的霍洛啟用設備的樣式。

我找到了一個相對乾淨的解決方案,對我來說工作得很好。

在主題中,我使用了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;
}

https://src-bin.com

我正在嘗試更改白色選項菜單的默認顏色:我希望選項菜單上的每個項目都有黑色背景。

我在菜單元素的item元素上嘗試了一些類似android:itemBackground =“#000000”的射擊,但它不起作用。

我怎樣才能做到這一點?


Answer #1

對於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版本中再次出現任何問題,您只需看到默認的菜單樣式即可


Answer #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>

下次你決定通過加拿大去超市試試谷歌地圖!


Answer #3

花費大量時間嘗試所有選項後,使用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。


Answer #4

菜單背景的樣式屬性是android:panelFullBackground

儘管文檔中提到,它需要是一個資源(例如@android:color/black@drawable/my_drawable ),如果直接使用顏色值,它會崩潰。

這也將擺脫使用primalpop的解決方案無法更改或刪除的項目邊界。

至於文本顏色,我還沒有找到任何方式來通過2.2中的樣式來設置它,我確信我已經嘗試了所有東西(這是我發現菜單背景屬性的方式)。 你需要使用primalpop的解決方案。


Answer #5

謝謝Marcus! 它通過修復一些語法錯誤來平滑地工作,這裡是固定的代碼

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

Answer #6

這是我解決我的問題。 我只是在樣式中指定了背景顏色和文字顏色。 即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>

Answer #7

這顯然是很多程序員所面臨的問題,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不可用。 所以我們必須使用能夠在當前版本中運行的解決方案,並且旨在最大限度地減少未來版本崩潰/崩潰的可能性。 我們想要一個解決方案,如果它必須失敗,才會優雅地恢復到默認行為。

有很多合法的原因可能需要控制選項菜單的外觀(通常為應用程序的其餘部分匹配視覺樣式),所以我不會詳述。

這裡有一個谷歌Android錯誤發布:請添加您的支持,通過這個bug的主演(注意谷歌不鼓勵“我也是”評論:只是一顆星星就夠了):

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

迄今為止的解決方案摘要:

幾個海報建議涉及LayoutInflater.Factory的破解。 建議的hack對於Android <= 2.2工作並且對於Android 2.3失敗,因為hack提出了一個無法證明的假設:可以直接調用LayoutInflater.getView(),而不需要在同一個LayoutInflater實例中對LayoutInflater.inflate()的調用。 Android 2.3中的新代碼打破了這個假設,導致了NullPointerException。

我下面略微精煉的黑客並不依賴於這個假設。

此外,黑客還依賴使用內部的,未公開的類名“com.android.internal.view.menu.IconMenuItemView”作為字符串(而不是Java類型)。 我看不出有什麼辦法可以避免這種情況,並且仍然達到了既定的目標。 但是,如果“com.android.internal.view.menu.IconMenuItemView”沒有出現在當前系統上,那麼可以小心翼翼地進行破解。

再次,了解這是一個破解,我絕不會聲稱這將適用於所有平台。 但是我們的開發人員並不是生活在一個夢幻般的學術世界裡,書中的一切都必須依靠:我們有一個需要解決的問題,我們必須盡我們所能解決問題。 例如,“3. com.android.internal.view.menu.IconMenuItemView”似乎不太可能存在於3.X平板電腦上,因為它們使用操作欄而不是選項菜單。

最後,一些開發者通過完全禁止Android選項菜單並編寫他們自己的菜單類來解決這個問題(參見上面的一些鏈接)。 我還沒有嘗試過,但如果你有時間寫自己的視圖並找出如何取代Android的視圖(我敢肯定魔鬼在這裡的細節),那麼它可能是一個很好的解決方案,不需要任何無證黑客。

HACK:

這是代碼。

要使用此代碼,請從您的活動onCreate()或您的活動onCreateOptionsMenu()調用addOptionsMenuHackerInflaterFactory()ONCE。 它設置了一個默認的工廠,將影響後續創建任何選項菜單。 它不會影響已經創建的選項菜單(以前的黑客使用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;
        }
    });
}

感謝閱讀和享受!


Answer #8
    /* 
     *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;
                }
            });
        }
    }

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

這對我來說很好


Answer #10
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




layout-inflater