2012年6月19日火曜日

Android DialogFragment では Dialog のサイズ指定は onActivityCreated でやれ

前のエントリでダイアログのサイズを指定する方法を紹介しましたが、 (Y.A.M の 雑記帳: Android Dialog の大きさを自分で設定する -) これを DialogFragment で行う場合、ちょっと注意が必要です。

DialogFragment には表示するダイアログのインスタンスを自分で作成するための onCreateDialog() というメソッドが用意されています。
このメソッドを Override して、任意の Dialog クラスのインスタンスを返すことでオリジナルのテーマを適用したダイアログを表示することができます。
public class MyDialogFragment extends DialogFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // ダイアログの中に表示する View を返す ... } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Dialog dialog = new Dialog(getActivity(), R.style.MyDialogTheme); return dialog; } } こうすると、MyDialogTheme を適用したダイアログになります。
とかすれば、タイトル部分のないダイアログにすることができます。


ダイアログのサイズを指定する方法で、この MyDialogFragment の大きさを指定しようとした場合、 public class MyDialogFragment extends DialogFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // ダイアログの中に表示する View を返す ... } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Dialog dialog = new Dialog(getActivity(), R.style.MyDialogTheme); WindowManager.LayoutParams lp = dialog.getWindow().getAttributes(); DisplayMetrics metrics = getResources().getDisplayMetrics(); int dialogWidth = (int) (metrics.widthPixels * 0.8); int dialogHeight = (int) (metrics.heightPixels * 0.8); lp.width = dialogWidth; lp.height = dialogHeight; dialog.getWindow().setAttributes(lp); return dialog; } } としてもなぜか適用されません。

DialogFragment の実装をみると getLayoutInflater() でフィールドの mDialog に onCreateDialog() の戻り値を格納しています。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/DialogFragment.java#391 391 /** @hide */ 392 @Override 393 public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { 394 if (!mShowsDialog) { 395 return super.getLayoutInflater(savedInstanceState); 396 } 397 398 mDialog = onCreateDialog(savedInstanceState); ... 415 } この getLayoutInflater() を呼び出す部分(FragmentManager)をみると

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/FragmentManager.java#795 795 f.mView = f.onCreateView(f.getLayoutInflater(f.mSavedFragmentState), 796 container, f.mSavedFragmentState); getLayoutInflater() を呼び出して、その結果を引数として onCreateView() を呼んでいます。

この後に DialogFragment の onActivityCreated() が呼ばれ、ここで onCreateView() で返した View がダイアログに setContentView() でセットされます。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/DialogFragment.java#456 456 @Override 457 public void onActivityCreated(Bundle savedInstanceState) { 458 super.onActivityCreated(savedInstanceState); 459 460 if (!mShowsDialog) { 461 return; 462 } 463 464 View view = getView(); 465 if (view != null) { 466 if (view.getParent() != null) { 467 throw new IllegalStateException("DialogFragment can not be attached to a container view"); 468 } 469 mDialog.setContentView(view); 470 } ... 483 } http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/Fragment.java#1106 1106 public View getView() { 1107 return mView; 1108 } つまり流れとしては

getLayoutInflater()
  → onCreateDialog()

onCreateView()

onActivityCreated()

のような順番になります。onCreateDialog() が一番最初です。

onActivityCreated() で Dialog の setContentView() が呼ばれるわけですが、Dialog の setContentView() は Window の setContentView() を呼び出しているだけです。 84 Window mWindow; ... 146 Dialog(Context context, int theme, boolean createContextWrapper) { ... 155 mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); 156 Window w = PolicyManager.makeNewWindow(mContext); 157 mWindow = w; ... 163 } ... 466 public void setContentView(View view) { 467 mWindow.setContentView(view); 468 } Window クラスの setContentView() は abstract メソッドで、

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/view/Window.java#922 922 public abstract void setContentView(View view); 実際は Window を継承した PhoneWindow などに実装があります。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java#264 258 @Override 259 public void setContentView(View view) { 260 setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 261 } 262 263 @Override 264 public void setContentView(View view, ViewGroup.LayoutParams params) { 265 if (mContentParent == null) { 266 installDecor(); 267 } else { 268 mContentParent.removeAllViews(); 269 } 270 mContentParent.addView(view, params); 271 final Callback cb = getCallback(); 272 if (cb != null && !isDestroyed()) { 273 cb.onContentChanged(); 274 } 275 } ここの setContentView() で呼ばれる installDecor() で呼ばれる generateLayout() で 2738 private void installDecor() { 2739 if (mDecor == null) { 2740 mDecor = generateDecor(); 2741 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 2742 mDecor.setIsRootNamespace(true); 2743 } 2744 if (mContentParent == null) { 2745 mContentParent = generateLayout(mDecor); 2746 ... 2498 protected ViewGroup generateLayout(DecorView decor) { 2499 // Apply data from current theme. 2500 2501 TypedArray a = getWindowStyle(); 2502 2503 if (false) { 2504 System.out.println("From style:"); 2505 String s = "Attrs:"; 2506 for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) { 2507 s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "=" 2508 + a.getString(i); 2509 } 2510 System.out.println(s); 2511 } 2512 2513 mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false); 2514 int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) 2515 & (~getForcedWindowFlags()); 2516 if (mIsFloating) { 2517 setLayout(WRAP_CONTENT, WRAP_CONTENT); 2518 setFlags(0, flagsToUpdate); 2519 } else { 2520 setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); 2521 } <item name="android:windowIsFloating">true</item> がスタイルに定義されていた場合、setLayout(WRAP_CONTENT, WRAP_CONTENT); を呼んでいます。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/view/Window.java#604 604 public void setLayout(int width, int height) { 605 final WindowManager.LayoutParams attrs = getAttributes(); 606 attrs.width = width; 607 attrs.height = height; 608 if (mCallback != null) { 609 mCallback.onWindowAttributesChanged(attrs); 610 } 611 } ありましたー。そう、Dialog の setContentView() を呼び出すと、ここで WindowManager.LayoutParams に WRAP_CONTENT, WRAP_CONTENT がセットされてしまうのです。

ということで、意図通りにするには public class MyDialogFragment extends DialogFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // ダイアログの中に表示する View を返す ... } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Dialog dialog = getDialog(); WindowManager.LayoutParams lp = dialog.getWindow().getAttributes(); DisplayMetrics metrics = getResources().getDisplayMetrics(); int dialogWidth = (int) (metrics.widthPixels * 0.8); int dialogHeight = (int) (metrics.heightPixels * 0.8); lp.width = dialogWidth; lp.height = dialogHeight; dialog.getWindow().setAttributes(lp); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Dialog dialog = new Dialog(getActivity(), R.style.MyDialogTheme); return dialog; } のように super.onActivityCreated() を呼んだ後に getDialog() で Dialog のインスタンスを取得して、それに対して WindowManager.LayoutParams をセットすれば OK です!










0 件のコメント:

コメントを投稿