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
  1. 1059     public void setCanceledOnTouchOutside(boolean cancel) {  
  2. 1060         if (cancel && !mCancelable) {  
  3. 1061             mCancelable = true;  
  4. 1062         }  
  5. 1063   
  6. 1064         mWindow.setCloseOnTouchOutside(cancel);  
  7. 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
  1. 912         public AlertDialog create() {  
  2. 913             final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);  
  3. 914             P.apply(dialog.mAlert);  
  4. 915             dialog.setCancelable(P.mCancelable);  
  5. 916             if (P.mCancelable) {  
  6. 917                 dialog.setCanceledOnTouchOutside(true);  
  7. 918             }  
  8. 919             dialog.setOnCancelListener(P.mOnCancelListener);  
  9. 920             if (P.mOnKeyListener != null) {  
  10. 921                 dialog.setOnKeyListener(P.mOnKeyListener);  
  11. 922             }  
  12. 923             return dialog;  
  13. 924         }  


次に Window の setCloseOnTouchOutside() を見たら

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/view/Window.java#setCloseOnTouchOutside
  1. 123     private boolean mCloseOnTouchOutside = false;  
  2. 124     private boolean mSetCloseOnTouchOutside = false;  
  3.   
  4. 812     public void setCloseOnTouchOutside(boolean close) {  
  5. 813         mCloseOnTouchOutside = close;  
  6. 814         mSetCloseOnTouchOutside = true;  
  7. 815     }  
  8. 816   
  9. 817     /** @hide */  
  10. 818     public void setCloseOnTouchOutsideIfNotSet(boolean close) {  
  11. 819         if (!mSetCloseOnTouchOutside) {  
  12. 820             mCloseOnTouchOutside = close;  
  13. 821             mSetCloseOnTouchOutside = true;  
  14. 822         }  
  15. 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
  1. 2567         if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion  
  2. 2568                 >= android.os.Build.VERSION_CODES.HONEYCOMB) {  
  3. 2569             if (a.getBoolean(  
  4. 2570                     com.android.internal.R.styleable.Window_windowCloseOnTouchOutside,  
  5. 2571                     false)) {  
  6. 2572                 setCloseOnTouchOutsideIfNotSet(true);  
  7. 2573             }  
  8. 2574         }  


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

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/values/themes.xml#174
  1. <style name="Theme">  
  2. ...  
  3.   <item name="windowCloseOnTouchOutside">false</item>  
  4. </style>  
  5.   
  6. <style name="Theme.Dialog">  
  7. ...  
  8. <item name="android:windowCloseOnTouchOutside">@bool/config_closeDialogWhenTouchOutside</item>  
  9. </style>  
  10.   
  11. <style name="Theme.Dialog.NoFrame">  
  12. ...  
  13. <item name="android:windowCloseOnTouchOutside">false</item>  
  14. </style>  
  15.   
  16. <style name="Theme.Toast" parent="@android:style/Theme.Dialog">  
  17. ...  
  18. <item name="android:windowCloseOnTouchOutside">false</item>  
  19. </style>  
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/values/config.xml#93
  1. <bool name="config_closeDialogWhenTouchOutside">true</bool>  


なるほど。

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

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


とかして

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


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



0 件のコメント:

コメントを投稿