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
- <resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="AppTheme" parent="android:Theme.Holo.Light">
- <item name="android:listChoiceBackgroundIndicator">@drawable/selector_dropdownlist</item>
- </style>
-
- </resources>
ただし!なぜかベースのテーマを Theme.Holo.Light.DarkActionBar にすると、この設定が効きません!(Theme.Holo、Theme.Holo.Light は効く)
原因はまだ見つけてません。ぐぬぬ
2013.12.3 追記
原因&解決方法を見つけました!
Theme.Holo.Light.DarkActionBar でセットされている属性を一つずつ追加していったところ、
- <item name="android:actionBarWidgetTheme">@android:style/Theme.Holo</item>
- @android:style/Theme.Holo
が原因だということがわかりました。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
- 800 public Context getThemedContext() {
- 801 if (mThemedContext == null) {
- 802 TypedValue outValue = new TypedValue();
- 803 Resources.Theme currentTheme = mContext.getTheme();
- 804 currentTheme.resolveAttribute(com.android.internal.R.attr.actionBarWidgetTheme,
- 805 outValue, true);
- 806 final int targetThemeRes = outValue.resourceId;
- 807
- 808 if (targetThemeRes != 0 && mContext.getThemeResId() != targetThemeRes) {
- 809 mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes);
- 810 } else {
- 811 mThemedContext = mContext;
- 812 }
- 813 }
- 814 return mThemedContext;
- 815 }
800 public Context getThemedContext() {
801 if (mThemedContext == null) {
802 TypedValue outValue = new TypedValue();
803 Resources.Theme currentTheme = mContext.getTheme();
804 currentTheme.resolveAttribute(com.android.internal.R.attr.actionBarWidgetTheme,
805 outValue, true);
806 final int targetThemeRes = outValue.resourceId;
807
808 if (targetThemeRes != 0 && mContext.getThemeResId() != targetThemeRes) {
809 mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes);
810 } else {
811 mThemedContext = mContext;
812 }
813 }
814 return mThemedContext;
815 }
そこで、Theme.Holo を継承し android:listChoiceBackgroundIndicator をセットしたテーマを別途用意し、android:actionBarWidgetTheme に指定するようにしたところうまくいきました。
- <resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="AppThemeHolo" parent="android:Theme.Holo">
- <item name="android:listChoiceBackgroundIndicator">@drawable/selector_dropdownlist</item>
- </style>
-
- <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
- <item name="android:listChoiceBackgroundIndicator">@drawable/selector_dropdownlist</item>
- <item name="android:actionBarWidgetTheme">@style/AppThemeHolo</item>
- </style>
-
- </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
- 122 public boolean tryShow() {
- 123 mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
- 124 mPopup.setOnDismissListener(this);
- 125 mPopup.setOnItemClickListener(this);
- 126 mPopup.setAdapter(mAdapter);
- 127 mPopup.setModal(true);
- 128
- 129 View anchor = mAnchorView;
- 130 if (anchor != null) {
- 131 final boolean addGlobalListener = mTreeObserver == null;
- 132 mTreeObserver = anchor.getViewTreeObserver();
- 133 if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
- 134 anchor.addOnAttachStateChangeListener(this);
- 135 mPopup.setAnchorView(anchor);
- 136 mPopup.setDropDownGravity(mDropDownGravity);
- 137 } else {
- 138 return false;
- 139 }
- 140
- 141 if (!mHasContentWidth) {
- 142 mContentWidth = measureContentWidth();
- 143 mHasContentWidth = true;
- 144 }
- 145
- 146 mPopup.setContentWidth(mContentWidth);
- 147 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
- 148 mPopup.show();
- 149 mPopup.getListView().setOnKeyListener(this);
- 150 return true;
- 151 }
122 public boolean tryShow() {
123 mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
124 mPopup.setOnDismissListener(this);
125 mPopup.setOnItemClickListener(this);
126 mPopup.setAdapter(mAdapter);
127 mPopup.setModal(true);
128
129 View anchor = mAnchorView;
130 if (anchor != null) {
131 final boolean addGlobalListener = mTreeObserver == null;
132 mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
133 if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
134 anchor.addOnAttachStateChangeListener(this);
135 mPopup.setAnchorView(anchor);
136 mPopup.setDropDownGravity(mDropDownGravity);
137 } else {
138 return false;
139 }
140
141 if (!mHasContentWidth) {
142 mContentWidth = measureContentWidth();
143 mHasContentWidth = true;
144 }
145
146 mPopup.setContentWidth(mContentWidth);
147 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
148 mPopup.show();
149 mPopup.getListView().setOnKeyListener(this);
150 return true;
151 }
ListPopupWindow には setListSelector() というメソッドが用意されています。
http://tools.oesf.biz/android-4.4.0_r1.0/xref/frameworks/base/core/java/android/widget/ListPopupWindow.java
- 350 public void setListSelector(Drawable selector) {
- 351 mDropDownListHighlight = selector;
- 352 }
- ...
- 971 private int buildDropDown() {
- 1015 private int buildDropDown() {
- 1016 ViewGroup dropDownView;
- 1017 int otherHeights = 0;
- 1018
- 1019 if (mDropDownList == null) {
- 1020 Context context = mContext;
- 1021
- 1022
-
-
-
-
-
- 1028 mShowDropDownRunnable = new Runnable() {
- 1029 public void run() {
- 1030
- 1031 View view = getAnchorView();
- 1032 if (view != null && view.getWindowToken() != null) {
- 1033 show();
- 1034 }
- 1035 }
- 1036 };
- 1037
- 1038 mDropDownList = new DropDownListView(context, !mModal);
- 1039 if (mDropDownListHighlight != null) {
- 1040 mDropDownList.setSelector(mDropDownListHighlight);
- 1041 }
- ...
350 public void setListSelector(Drawable selector) {
351 mDropDownListHighlight = selector;
352 }
...
971 private int buildDropDown() {
1015 private int buildDropDown() {
1016 ViewGroup dropDownView;
1017 int otherHeights = 0;
1018
1019 if (mDropDownList == null) {
1020 Context context = mContext;
1021
1022 /**
1023 * This Runnable exists for the sole purpose of checking if the view layout has got
1024 * completed and if so call showDropDown to display the drop down. This is used to show
1025 * the drop down as soon as possible after user opens up the search dialog, without
1026 * waiting for the normal UI pipeline to do it's job which is slower than this method.
1027 */
1028 mShowDropDownRunnable = new Runnable() {
1029 public void run() {
1030 // View layout should be all done before displaying the drop down.
1031 View view = getAnchorView();
1032 if (view != null && view.getWindowToken() != null) {
1033 show();
1034 }
1035 }
1036 };
1037
1038 mDropDownList = new DropDownListView(context, !mModal);
1039 if (mDropDownListHighlight != null) {
1040 mDropDownList.setSelector(mDropDownListHighlight);
1041 }
...
しかし、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
- 1445 public DropDownListView(Context context, boolean hijackFocus) {
- 1446 super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
- 1447 mHijackFocus = hijackFocus;
- 1448
- 1449 setCacheColorHint(0);
- 1450 }
1445 public DropDownListView(Context context, boolean hijackFocus) {
1446 super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
1447 mHijackFocus = hijackFocus;
1448 // TODO: Add an API to control this
1449 setCacheColorHint(0); // Transparent, since the background drawable could be anything.
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
- 906 <style name="Theme.Holo">
- 1087 <item name="dropDownListViewStyle">@android:style/Widget.Holo.ListView.DropDown</item>
- 1214 </style>
- 1221 <style name="Theme.Holo.Light" parent="Theme.Light">
- 1402 <item name="dropDownListViewStyle">@android:style/Widget.Holo.ListView.DropDown</item>
- 1529 </style>
906
1221
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 をセットしています。
- 1649 <style name="Widget.Holo.ListView.DropDown">
- 1650 </style>
- 1715 <style name="Widget.Holo.ListView" parent="Widget.ListView">
- 1716 <item name="android:divider">?android:attr/listDivider</item>
- 1717 <item name="android:listSelector">?android:attr/listChoiceBackgroundIndicator</item>
- 1718 </style>
1649
1715
よって、android:listChoiceBackgroundIndicator に変更したい selector を指定すればいいということになります。