2012年7月26日木曜日

Android Honeycomb 以降ではダイアログの外部タッチで閉じる、がデフォルトになっている

ダイアログの周りの領域をタッチしたときにダイアログを閉じるかどうかは setCanceledOnTouchOutside() で設定できます。

Honeycomb 以降では、ダイアログの外部をタッチしたときにダイアログが閉じるようになっているので、それが嫌な場合は Dialog のインスタンスに対して setCanceledOnTouchOutside(false) を呼べば OK です。

DialogFragment なら onActivityCreated() で getDialog() で Dialog のインスタンスを取得できます。


なんでこうなってるのかを調べたら意外に深かった。。。

まず、Dialog の setCanceledOnTouchOutSide() は内部では Window に対して setCloseOnTouchOutside() を呼んでいます。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/Dialog.java#1059
1059 public void setCanceledOnTouchOutside(boolean cancel) { 1060 if (cancel && !mCancelable) { 1061 mCancelable = true; 1062 } 1063 1064 mWindow.setCloseOnTouchOutside(cancel); 1065 }

Dialog クラス内では setCanceledOnTouchOutside() を呼んでいません。 検索しても呼んでるのは AlertDialog.Builder、TimePickerDialog、SerchDialog のみ。

ちなみに AlertDialog.Builder では create() 時にキャンセル可能なら true をセットしています。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/AlertDialog.java#917
912 public AlertDialog create() { 913 final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false); 914 P.apply(dialog.mAlert); 915 dialog.setCancelable(P.mCancelable); 916 if (P.mCancelable) { 917 dialog.setCanceledOnTouchOutside(true); 918 } 919 dialog.setOnCancelListener(P.mOnCancelListener); 920 if (P.mOnKeyListener != null) { 921 dialog.setOnKeyListener(P.mOnKeyListener); 922 } 923 return dialog; 924 }

次に Window の setCloseOnTouchOutside() を見たら

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/view/Window.java#setCloseOnTouchOutside 123 private boolean mCloseOnTouchOutside = false; 124 private boolean mSetCloseOnTouchOutside = false; 812 public void setCloseOnTouchOutside(boolean close) { 813 mCloseOnTouchOutside = close; 814 mSetCloseOnTouchOutside = true; 815 } 816 817 /** @hide */ 818 public void setCloseOnTouchOutsideIfNotSet(boolean close) { 819 if (!mSetCloseOnTouchOutside) { 820 mCloseOnTouchOutside = close; 821 mSetCloseOnTouchOutside = true; 822 } 823 }

mCloseOnTouchOutside に値をセットしている。この変数の初期値は false で、値をセットしているのは setCloseOnTouchOutside() と setCloseOnTouchOutsideIfNotSet() だけ、このどちらかがデフォルトで呼ばれていないと初期値が false なのでつじつまが合わない。

setCloseOnTouchOutside は Dialog と Activity からしか呼ばれていないし、Activity で呼んでいるところは関係なかった。

setCloseOnTouchOutsideIfNotSet で検索したら AlertController と PhoneWindow から呼ばれてる。 PhoneWindow を見ると

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java#2567 2567 if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion 2568 >= android.os.Build.VERSION_CODES.HONEYCOMB) { 2569 if (a.getBoolean( 2570 com.android.internal.R.styleable.Window_windowCloseOnTouchOutside, 2571 false)) { 2572 setCloseOnTouchOutsideIfNotSet(true); 2573 } 2574 }

ビンゴ。ここで HONEYCOMB がでてくる。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/values/themes.xml#174 <style name="Theme"> ... <item name="windowCloseOnTouchOutside">false</item> </style> <style name="Theme.Dialog"> ... <item name="android:windowCloseOnTouchOutside">@bool/config_closeDialogWhenTouchOutside</item> </style> <style name="Theme.Dialog.NoFrame"> ... <item name="android:windowCloseOnTouchOutside">false</item> </style> <style name="Theme.Toast" parent="@android:style/Theme.Dialog"> ... <item name="android:windowCloseOnTouchOutside">false</item> </style> http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/values/config.xml#93 <bool name="config_closeDialogWhenTouchOutside">true</bool>

なるほど。

ということは、DialogFragment でオリジナルテーマ設定してるなら

<style name="MyDialogTheme" parent="@android:style/Theme.Holo.Light.Dialog"> ... <item name="android:windowCloseOnTouchOutside">false</item> </style>

とかして

@Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new Dialog(getActivity(), R.style.MyDialogTheme); }

とすれば、このテーマを使ってるもろもろの DialogFragment を一括で変更できたー!



0 件のコメント:

コメントを投稿