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

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


2013.12.3 追記

原因&解決方法を見つけました!
Theme.Holo.Light.DarkActionBar でセットされている属性を一つずつ追加していったところ、 @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 } そこで、Theme.Holo を継承し android:listChoiceBackgroundIndicator をセットしたテーマを別途用意し、android:actionBarWidgetTheme に指定するようにしたところうまくいきました。




■ 詳細解説

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(); // 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 /** 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 // 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 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 1715 よって、android:listChoiceBackgroundIndicator に変更したい selector を指定すればいいということになります。



0 件のコメント:

コメントを投稿