2013年12月2日月曜日

ActionBar の Overflow Menu の selector の色を変える

Android UI Cookbook for 4.0 で紹介した方法だと、適用する selector が不透明の場合はいいのですが、半透明の場合問題が起こります。

以下は、Android UI Cookbook for 4.0 で紹介した方法(android:itemBackground を指定する方法)で、押したとき背景が半透明の赤になるようにした場合です。



赤とデフォルトの水色が混ざって、紫っぽくなってしまっています。
これは、半透明の赤の下にデフォルトの水色の selector も表示されているからです。

これを修正する方法は2つあります。
  • 1. android:itemBackground には何も指定せず、下の水色の selector を変更する
  • 2. android:itemBackground を指定して、下の水色の selector に透明を指定する
いずれにしろ、この水色の selector を変更しないといけません。
結論としては、android:listChoiceBackgroundIndicator を利用します。この属性は API Level 11 からですが、Support Library v7 の appcompat でも対応しています。

res/values/styles.xml
  1. <resources xmlns:android="http://schemas.android.com/apk/res/android">  
  2.   
  3.     <style name="AppTheme" parent="android:Theme.Holo.Light">  
  4.         <item name="android:listChoiceBackgroundIndicator">@drawable/selector_dropdownlist</item>  
  5.     </style>  
  6.   
  7. </resources>  


ただし!なぜかベースのテーマを Theme.Holo.Light.DarkActionBar にすると、この設定が効きません!(Theme.Holo、Theme.Holo.Light は効く)
原因はまだ見つけてません。ぐぬぬ


2013.12.3 追記

原因&解決方法を見つけました!
Theme.Holo.Light.DarkActionBar でセットされている属性を一つずつ追加していったところ、
  1. <item name="android:actionBarWidgetTheme">@android:style/Theme.Holo</item>  
が原因だということがわかりました。Theme.Holo および Theme.Holo.Light ではこの属性には @null が指定されています。

この属性は ActionBarImpl クラスで利用されています。

http://tools.oesf.biz/android-4.4.0_r1.0/xref/frameworks/base/core/java/com/android/internal/app/ActionBarImpl.java#800
  1. 800     public Context getThemedContext() {  
  2. 801         if (mThemedContext == null) {  
  3. 802             TypedValue outValue = new TypedValue();  
  4. 803             Resources.Theme currentTheme = mContext.getTheme();  
  5. 804             currentTheme.resolveAttribute(com.android.internal.R.attr.actionBarWidgetTheme,  
  6. 805                     outValue, true);  
  7. 806             final int targetThemeRes = outValue.resourceId;  
  8. 807   
  9. 808             if (targetThemeRes != 0 && mContext.getThemeResId() != targetThemeRes) {  
  10. 809                 mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes);  
  11. 810             } else {  
  12. 811                 mThemedContext = mContext;  
  13. 812             }  
  14. 813         }  
  15. 814         return mThemedContext;  
  16. 815     }  
そこで、Theme.Holo を継承し android:listChoiceBackgroundIndicator をセットしたテーマを別途用意し、android:actionBarWidgetTheme に指定するようにしたところうまくいきました。
  1. <resources xmlns:android="http://schemas.android.com/apk/res/android">  
  2.   
  3.     <style name="AppThemeHolo" parent="android:Theme.Holo">  
  4.         <item name="android:listChoiceBackgroundIndicator">@drawable/selector_dropdownlist</item>  
  5.     </style>  
  6.       
  7.     <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">  
  8.         <item name="android:listChoiceBackgroundIndicator">@drawable/selector_dropdownlist</item>  
  9.         <item name="android:actionBarWidgetTheme">@style/AppThemeHolo</item>  
  10.     </style>  
  11.   
  12. </resources>  





■ 詳細解説

Overflow Menu は ListPopupWindow です。

http://tools.oesf.biz/android-4.4.0_r1.0/xref/frameworks/base/core/java/com/android/internal/view/menu/MenuPopupHelper.java#122
  1. 122     public boolean tryShow() {  
  2. 123         mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);  
  3. 124         mPopup.setOnDismissListener(this);  
  4. 125         mPopup.setOnItemClickListener(this);  
  5. 126         mPopup.setAdapter(mAdapter);  
  6. 127         mPopup.setModal(true);  
  7. 128   
  8. 129         View anchor = mAnchorView;  
  9. 130         if (anchor != null) {  
  10. 131             final boolean addGlobalListener = mTreeObserver == null;  
  11. 132             mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest  
  12. 133             if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);  
  13. 134             anchor.addOnAttachStateChangeListener(this);  
  14. 135             mPopup.setAnchorView(anchor);  
  15. 136             mPopup.setDropDownGravity(mDropDownGravity);  
  16. 137         } else {  
  17. 138             return false;  
  18. 139         }  
  19. 140   
  20. 141         if (!mHasContentWidth) {  
  21. 142             mContentWidth = measureContentWidth();  
  22. 143             mHasContentWidth = true;  
  23. 144         }  
  24. 145   
  25. 146         mPopup.setContentWidth(mContentWidth);  
  26. 147         mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);  
  27. 148         mPopup.show();  
  28. 149         mPopup.getListView().setOnKeyListener(this);  
  29. 150         return true;  
  30. 151     }  
ListPopupWindow には setListSelector() というメソッドが用意されています。

http://tools.oesf.biz/android-4.4.0_r1.0/xref/frameworks/base/core/java/android/widget/ListPopupWindow.java
  1.     350     public void setListSelector(Drawable selector) {  
  2.     351         mDropDownListHighlight = selector;  
  3.     352     }  
  4. ...  
  5.     971     private int buildDropDown() {  
  6.    1015     private int buildDropDown() {  
  7.    1016         ViewGroup dropDownView;  
  8.    1017         int otherHeights = 0;  
  9.    1018   
  10.    1019         if (mDropDownList == null) {  
  11.    1020             Context context = mContext;  
  12.    1021   
  13.    1022             /** 
  14.    1023              * This Runnable exists for the sole purpose of checking if the view layout has got 
  15.    1024              * completed and if so call showDropDown to display the drop down. This is used to show 
  16.    1025              * the drop down as soon as possible after user opens up the search dialog, without 
  17.    1026              * waiting for the normal UI pipeline to do it's job which is slower than this method. 
  18.    1027              */  
  19.    1028             mShowDropDownRunnable = new Runnable() {  
  20.    1029                 public void run() {  
  21.    1030                     // View layout should be all done before displaying the drop down.  
  22.    1031                     View view = getAnchorView();  
  23.    1032                     if (view != null && view.getWindowToken() != null) {  
  24.    1033                         show();  
  25.    1034                     }  
  26.    1035                 }  
  27.    1036             };  
  28.    1037   
  29.    1038             mDropDownList = new DropDownListView(context, !mModal);  
  30.    1039             if (mDropDownListHighlight != null) {  
  31.    1040                 mDropDownList.setSelector(mDropDownListHighlight);  
  32.    1041             }  
  33. ...  
しかし、MenuPopupHelper では、setListSelector() を呼んでくれていません。

ListPopupWindow 内のリストは、DropDownListView です。 このクラスは ListPopupWindow の内部クラスです。

http://tools.oesf.biz/android-4.4.0_r1.0/xref/frameworks/base/core/java/android/widget/ListPopupWindow.java#1445
  1. 1445         public DropDownListView(Context context, boolean hijackFocus) {  
  2. 1446             super(context, null, com.android.internal.R.attr.dropDownListViewStyle);  
  3. 1447             mHijackFocus = hijackFocus;  
  4. 1448             // TODO: Add an API to control this  
  5. 1449             setCacheColorHint(0); // Transparent, since the background drawable could be anything.  
  6. 1450         }  
ここでは com.android.internal.R.attr.dropDownListViewStyle を defStyleAttr として渡しています。

よって、android:dropDownListViewStyle を指定すればいいということです。Holo テーマでどのようなスタイルになっているか見てみましょう。

http://tools.oesf.biz/android-4.4.0_r1.0/xref/frameworks/base/core/res/res/values/themes.xml
  1.  906     <style name="Theme.Holo">  
  2. 1087         <item name="dropDownListViewStyle">@android:style/Widget.Holo.ListView.DropDown</item>  
  3. 1214     </style>  
  4. 1221     <style name="Theme.Holo.Light" parent="Theme.Light">  
  5. 1402         <item name="dropDownListViewStyle">@android:style/Widget.Holo.ListView.DropDown</item>  
  6. 1529     </style>  
Theme.Holo も Theme.Holo.Light も @android:style/Widget.Holo.ListView.DropDown がセットされています。

Widget.Holo.ListView.DropDown は Widget.Holo.ListView と同じで、Widget.Holo.ListView では android:divider として ?android:attr/listDivider を、android:listSelector として ?android:attr/listChoiceBackgroundIndicator をセットしています。
  1. 1649     <style name="Widget.Holo.ListView.DropDown">  
  2. 1650     </style>  
  3. 1715     <style name="Widget.Holo.ListView" parent="Widget.ListView">  
  4. 1716         <item name="android:divider">?android:attr/listDivider</item>  
  5. 1717         <item name="android:listSelector">?android:attr/listChoiceBackgroundIndicator</item>  
  6. 1718     </style>  
よって、android:listChoiceBackgroundIndicator に変更したい selector を指定すればいいということになります。



0 件のコメント:

コメントを投稿