developer tip

옵션 메뉴의 배경색을 변경하는 방법은 무엇입니까?

optionbox 2020. 8. 30. 08:10
반응형

옵션 메뉴의 배경색을 변경하는 방법은 무엇입니까?


흰색 인 옵션 메뉴의 기본 색상을 변경하려고합니다. 옵션 메뉴의 모든 항목에 검정색 배경이 필요합니다.

메뉴 요소 내의 항목 요소에서 android : itemBackground = "# 000000"과 같은 촬영을 시도했지만 작동하지 않았습니다.

어떻게 할 수 있습니까?


모든 옵션을 시도하는 데 상당한 시간을 보낸 후 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으로 테스트되었습니다.


이것은 많은 프로그래머가 가지고있는 문제이며 Google이 아직 만족스럽고 지원되는 솔루션을 제공하지 않은 것입니다.

이 주제에 대한 게시물 주위에는 교차 의도와 오해가 많이 있으므로 응답하기 전에 전체 답변을 읽으십시오.

아래에는이 페이지의 다른 답변에서 나온 해킹의 "정교화"되고 잘 설명 된 버전이 포함되어 있으며 매우 밀접하게 관련된 질문의 아이디어도 포함됩니다.

안드로이드 메뉴의 배경색 변경

옵션 메뉴의 배경색을 변경하는 방법은 무엇입니까?

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"가 나타나지 않으면 폴백되는 신중한 방법으로 해킹을 할 수 있습니다.

다시 말하지만 이것이 해킹이라는 것을 이해하고 이것이 모든 플랫폼에서 작동한다고 주장하는 것은 아닙니다. 그러나 우리 개발자들은 모든 것이 책에 의해 이루어져야하는 판타지 학술 세계에 살고 있지 않습니다. 우리는 해결해야 할 문제가 있고 가능한 한 최선을 다해 해결해야합니다. 예를 들어 3.X 태블릿에는 옵션 메뉴 대신 작업 표시 줄을 사용하기 때문에 "com.android.internal.view.menu.IconMenuItemView"가 존재하지 않을 것 같습니다.

마지막으로 일부 개발자는 Android 옵션 메뉴를 완전히 억제하고 자체 메뉴 클래스를 작성하여이 문제를 해결했습니다 (위의 일부 링크 참조). 나는 이것을 시도하지 않았지만 자신의 뷰를 작성하고 Android의 뷰를 대체하는 방법을 알아낼 시간이 있다면 (여기 세부 사항에 악마가 있다고 확신합니다) 필요하지 않은 멋진 솔루션 일 수 있습니다 문서화되지 않은 해킹.

마구 자르기:

다음은 코드입니다.

이 코드를 사용하려면 활동 onCreate () 또는 활동 onCreateOptionsMenu ()에서 addOptionsMenuHackerInflaterFactory ()를 한 번 호출하십시오. 옵션 메뉴의 후속 생성에 영향을 미치는 기본 공장을 설정합니다. 이미 생성 된 옵션 메뉴에는 영향을주지 않습니다 (이전 해킹은 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;
        }
    });
}

읽어 주셔서 감사합니다!


메뉴 배경의 스타일 속성은 android:panelFullBackground입니다.

문서에 나와있는 내용에도 불구하고 리소스 (예 : @android:color/black또는 @drawable/my_drawable) 여야 하지만 색상 값을 직접 사용하면 충돌이 발생합니다.

이렇게하면 primalpop의 솔루션을 사용하여 변경하거나 제거 할 수 없었던 항목 테두리도 제거됩니다.

텍스트 색상에 관해서는 2.2에서 스타일을 통해 설정하는 방법을 찾지 못했고 모든 것을 시도했다고 확신합니다 (메뉴 배경 속성을 발견 한 방법). 이를 위해 primalpop의 솔루션을 사용해야합니다.


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 버전에서 문제가 다시 발생하면 대신 기본 메뉴 스타일이 표시됩니다.


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

이것이 내가 내 것을 해결 한 방법입니다. 스타일에서 배경색과 텍스트 색상을 지정했습니다. ie 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>

여러분이 다른 많은 게시물과 마찬가지로 문제를 지나치게 복잡하게 만들고 있다는 점에 유의해야합니다! 필요한 모든 배경으로 드로어 블 선택기를 만들고 실제 항목으로 설정하기 만하면됩니다. 솔루션을 시도하는 데 두 시간을 소비했지만 (이 페이지에서 모두 제 안됨) 어느 것도 작동하지 않았습니다. 당신이 가지고있는 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지도를 사용해보세요!


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

이것은 나를 위해 잘 작동합니다


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

감사합니다 마커스! 일부 구문 오류를 수정하여 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
    }
}

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

임의의 색상을 설정하려면 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당신이 짐작했듯이 배경색을 그리는의 색상을 설정합니다. 오버 드로우가 없으며 반투명 색상도 사용할 수 있습니다.

참고 URL : https://stackoverflow.com/questions/2944244/how-to-change-the-background-color-of-the-options-menu

반응형