DialogFragment.java revision 7187ccb93ee8adbb745fcbb901cfacfeed397a23
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.app; 18 19import android.content.Context; 20import android.content.DialogInterface; 21import android.os.Bundle; 22import android.view.LayoutInflater; 23import android.view.View; 24import android.view.ViewGroup; 25import android.view.Window; 26import android.view.WindowManager; 27 28/** 29 * A fragment that displays a dialog window, floating on top of its 30 * activity's window. This fragment contains a Dialog object, which it 31 * displays as appropriate based on the fragment's state. Control of 32 * the dialog (deciding when to show, hide, dismiss it) should be done through 33 * the API here, not with direct calls on the dialog. 34 * 35 * <p>Implementations should override this class and implement 36 * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} to supply the 37 * content of the dialog. Alternatively, they can override 38 * {@link #onCreateDialog(Bundle)} to create an entirely custom dialog, such 39 * as an AlertDialog, with its own content. 40 * 41 * <p>Topics covered here: 42 * <ol> 43 * <li><a href="#Lifecycle">Lifecycle</a> 44 * <li><a href="#BasicDialog">Basic Dialog</a> 45 * <li><a href="#AlertDialog">Alert Dialog</a> 46 * <li><a href="#DialogOrEmbed">Selecting Between Dialog or Embedding</a> 47 * </ol> 48 * 49 * <a name="Lifecycle"></a> 50 * <h3>Lifecycle</h3> 51 * 52 * <p>DialogFragment does various things to keep the fragment's lifecycle 53 * driving it, instead of the Dialog. Note that dialogs are generally 54 * autonomous entities -- they are their own window, receiving their own 55 * input events, and often deciding on their own when to disappear (by 56 * receiving a back key event or the user clicking on a button). 57 * 58 * <p>DialogFragment needs to ensure that what is happening with the Fragment 59 * and Dialog states remains consistent. To do this, it watches for dismiss 60 * events from the dialog and takes are of removing its own state when they 61 * happen. This means you should use {@link #show(FragmentManager, String)} 62 * or {@link #show(FragmentTransaction, String)} to add an instance of 63 * DialogFragment to your UI, as these keep track of how DialogFragment should 64 * remove itself when the dialog is dismissed. 65 * 66 * <a name="BasicDialog"></a> 67 * <h3>Basic Dialog</h3> 68 * 69 * <p>The simplest use of DialogFragment is as a floating container for the 70 * fragment's view hierarchy. A simple implementation may look like this: 71 * 72 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialog.java 73 * dialog} 74 * 75 * <p>An example showDialog() method on the Activity could be: 76 * 77 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialog.java 78 * add_dialog} 79 * 80 * <p>This removes any currently shown dialog, creates a new DialogFragment 81 * with an argument, and shows it as a new state on the back stack. When the 82 * transaction is popped, the current DialogFragment and its Dialog will be 83 * destroyed, and the previous one (if any) re-shown. Note that in this case 84 * DialogFragment will take care of popping the transaction of the Dialog 85 * is dismissed separately from it. 86 * 87 * <a name="AlertDialog"></a> 88 * <h3>Alert Dialog</h3> 89 * 90 * <p>Instead of (or in addition to) implementing {@link #onCreateView} to 91 * generate the view hierarchy inside of a dialog, you may implement 92 * {@link #onCreateDialog(Bundle)} to create your own custom Dialog object. 93 * 94 * <p>This is most useful for creating an {@link AlertDialog}, allowing you 95 * to display standard alerts to the user that are managed by a fragment. 96 * A simple example implementation of this is: 97 * 98 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentAlertDialog.java 99 * dialog} 100 * 101 * <p>The activity creating this fragment may have the following methods to 102 * show the dialog and receive results from it: 103 * 104 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentAlertDialog.java 105 * activity} 106 * 107 * <p>Note that in this case the fragment is not placed on the back stack, it 108 * is just added as an indefinitely running fragment. Because dialogs normally 109 * are modal, this will still operate as a back stack, since the dialog will 110 * capture user input until it is dismissed. When it is dismissed, DialogFragment 111 * will take care of removing itself from its fragment manager. 112 * 113 * <a name="DialogOrEmbed"></a> 114 * <h3>Selecting Between Dialog or Embedding</h3> 115 * 116 * <p>A DialogFragment can still optionally be used as a normal fragment, if 117 * desired. This is useful if you have a fragment that in some cases should 118 * be shown as a dialog and others embedded in a larger UI. This behavior 119 * will normally be automatically selected for you based on how you are using 120 * the fragment, but can be customized with {@link #setShowsDialog(boolean)}. 121 * 122 * <p>For example, here is a simple dialog fragment: 123 * 124 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java 125 * dialog} 126 * 127 * <p>An instance of this fragment can be created and shown as a dialog: 128 * 129 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java 130 * show_dialog} 131 * 132 * <p>It can also be added as content in a view hierarchy: 133 * 134 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java 135 * embed} 136 */ 137public class DialogFragment extends Fragment 138 implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { 139 140 /** 141 * Style for {@link #setStyle(int, int)}: a basic, 142 * normal dialog. 143 */ 144 public static final int STYLE_NORMAL = 0; 145 146 /** 147 * Style for {@link #setStyle(int, int)}: don't include 148 * a title area. 149 */ 150 public static final int STYLE_NO_TITLE = 1; 151 152 /** 153 * Style for {@link #setStyle(int, int)}: don't draw 154 * any frame at all; the view hierarchy returned by {@link #onCreateView} 155 * is entirely responsible for drawing the dialog. 156 */ 157 public static final int STYLE_NO_FRAME = 2; 158 159 /** 160 * Style for {@link #setStyle(int, int)}: like 161 * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog. 162 * The user can not touch it, and its window will not receive input focus. 163 */ 164 public static final int STYLE_NO_INPUT = 3; 165 166 private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState"; 167 private static final String SAVED_STYLE = "android:style"; 168 private static final String SAVED_THEME = "android:theme"; 169 private static final String SAVED_CANCELABLE = "android:cancelable"; 170 private static final String SAVED_SHOWS_DIALOG = "android:showsDialog"; 171 private static final String SAVED_BACK_STACK_ID = "android:backStackId"; 172 173 int mStyle = STYLE_NORMAL; 174 int mTheme = 0; 175 boolean mCancelable = true; 176 boolean mShowsDialog = true; 177 int mBackStackId = -1; 178 179 Dialog mDialog; 180 boolean mDestroyed; 181 boolean mRemoved; 182 183 public DialogFragment() { 184 } 185 186 /** 187 * Call to customize the basic appearance and behavior of the 188 * fragment's dialog. This can be used for some common dialog behaviors, 189 * taking care of selecting flags, theme, and other options for you. The 190 * same effect can be achieve by manually setting Dialog and Window 191 * attributes yourself. Calling this after the fragment's Dialog is 192 * created will have no effect. 193 * 194 * @param style Selects a standard style: may be {@link #STYLE_NORMAL}, 195 * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or 196 * {@link #STYLE_NO_INPUT}. 197 * @param theme Optional custom theme. If 0, an appropriate theme (based 198 * on the style) will be selected for you. 199 */ 200 public void setStyle(int style, int theme) { 201 mStyle = style; 202 if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { 203 mTheme = com.android.internal.R.style.Theme_Holo_Dialog_NoFrame; 204 } 205 if (theme != 0) { 206 mTheme = theme; 207 } 208 } 209 210 /** 211 * Display the dialog, adding the fragment to the given FragmentManager. This 212 * is a convenience for explicitly creating a transaction, adding the 213 * fragment to it with the given tag, and committing it. This does 214 * <em>not</em> add the transaction to the back stack. When the fragment 215 * is dismissed, a new transaction will be executed to remove it from 216 * the activity. 217 * @param manager The FragmentManager this fragment will be added to. 218 * @param tag The tag for this fragment, as per 219 * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. 220 */ 221 public void show(FragmentManager manager, String tag) { 222 FragmentTransaction ft = manager.beginTransaction(); 223 ft.add(this, tag); 224 ft.commit(); 225 } 226 227 /** 228 * Display the dialog, adding the fragment using an existing transaction 229 * and then committing the transaction. 230 * @param transaction An existing transaction in which to add the fragment. 231 * @param tag The tag for this fragment, as per 232 * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. 233 * @return Returns the identifier of the committed transaction, as per 234 * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. 235 */ 236 public int show(FragmentTransaction transaction, String tag) { 237 transaction.add(this, tag); 238 mRemoved = false; 239 mBackStackId = transaction.commit(); 240 return mBackStackId; 241 } 242 243 /** 244 * Dismiss the fragment and its dialog. If the fragment was added to the 245 * back stack, all back stack state up to and including this entry will 246 * be popped. Otherwise, a new transaction will be committed to remove 247 * the fragment. 248 */ 249 public void dismiss() { 250 dismissInternal(false); 251 } 252 253 void dismissInternal(boolean allowStateLoss) { 254 if (mDialog != null) { 255 mDialog.dismiss(); 256 mDialog = null; 257 } 258 mRemoved = true; 259 if (mBackStackId >= 0) { 260 getFragmentManager().popBackStack(mBackStackId, 261 FragmentManager.POP_BACK_STACK_INCLUSIVE); 262 mBackStackId = -1; 263 } else { 264 FragmentTransaction ft = getFragmentManager().beginTransaction(); 265 ft.remove(this); 266 if (allowStateLoss) { 267 ft.commitAllowingStateLoss(); 268 } else { 269 ft.commit(); 270 } 271 } 272 } 273 274 public Dialog getDialog() { 275 return mDialog; 276 } 277 278 public int getTheme() { 279 return mTheme; 280 } 281 282 /** 283 * Control whether the shown Dialog is cancelable. Use this instead of 284 * directly calling {@link Dialog#setCancelable(boolean) 285 * Dialog.setCancelable(boolean)}, because DialogFragment needs to change 286 * its behavior based on this. 287 * 288 * @param cancelable If true, the dialog is cancelable. The default 289 * is true. 290 */ 291 public void setCancelable(boolean cancelable) { 292 mCancelable = cancelable; 293 if (mDialog != null) mDialog.setCancelable(cancelable); 294 } 295 296 /** 297 * Return the current value of {@link #setCancelable(boolean)}. 298 */ 299 public boolean isCancelable() { 300 return mCancelable; 301 } 302 303 /** 304 * Controls whether this fragment should be shown in a dialog. If not 305 * set, no Dialog will be created in {@link #onActivityCreated(Bundle)}, 306 * and the fragment's view hierarchy will thus not be added to it. This 307 * allows you to instead use it as a normal fragment (embedded inside of 308 * its activity). 309 * 310 * <p>This is normally set for you based on whether the fragment is 311 * associated with a container view ID passed to 312 * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}. 313 * If the fragment was added with a container, setShowsDialog will be 314 * initialized to false; otherwise, it will be true. 315 * 316 * @param showsDialog If true, the fragment will be displayed in a Dialog. 317 * If false, no Dialog will be created and the fragment's view hierarchly 318 * left undisturbed. 319 */ 320 public void setShowsDialog(boolean showsDialog) { 321 mShowsDialog = showsDialog; 322 } 323 324 /** 325 * Return the current value of {@link #setShowsDialog(boolean)}. 326 */ 327 public boolean getShowsDialog() { 328 return mShowsDialog; 329 } 330 331 @Override 332 public void onCreate(Bundle savedInstanceState) { 333 super.onCreate(savedInstanceState); 334 335 mShowsDialog = mContainerId == 0; 336 337 if (savedInstanceState != null) { 338 mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL); 339 mTheme = savedInstanceState.getInt(SAVED_THEME, 0); 340 mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true); 341 mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); 342 mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1); 343 } 344 345 } 346 347 /** @hide */ 348 @Override 349 public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { 350 if (!mShowsDialog) { 351 return super.getLayoutInflater(savedInstanceState); 352 } 353 354 mDialog = onCreateDialog(savedInstanceState); 355 mDestroyed = false; 356 switch (mStyle) { 357 case STYLE_NO_INPUT: 358 mDialog.getWindow().addFlags( 359 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 360 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); 361 // fall through... 362 case STYLE_NO_FRAME: 363 case STYLE_NO_TITLE: 364 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 365 } 366 return (LayoutInflater)mDialog.getContext().getSystemService( 367 Context.LAYOUT_INFLATER_SERVICE); 368 } 369 370 /** 371 * Override to build your own custom Dialog container. This is typically 372 * used to show an AlertDialog instead of a generic Dialog; when doing so, 373 * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need 374 * to be implemented since the AlertDialog takes care of its own content. 375 * 376 * <p>This method will be called after {@link #onCreate(Bundle)} and 377 * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. The 378 * default implementation simply instantiates and returns a {@link Dialog} 379 * class. 380 * 381 * @param savedInstanceState The last saved instance state of the Fragment, 382 * or null if this is a freshly created Fragment. 383 * 384 * @return Return a new Dialog instance to be displayed by the Fragment. 385 */ 386 public Dialog onCreateDialog(Bundle savedInstanceState) { 387 return new Dialog(getActivity(), getTheme()); 388 } 389 390 public void onCancel(DialogInterface dialog) { 391 } 392 393 public void onDismiss(DialogInterface dialog) { 394 if (!mRemoved) { 395 // Note: we need to use allowStateLoss, because the dialog 396 // dispatches this asynchronously so we can receive the call 397 // after the activity is paused. Worst case, when the user comes 398 // back to the activity they see the dialog again. 399 dismissInternal(true); 400 } 401 } 402 403 @Override 404 public void onActivityCreated(Bundle savedInstanceState) { 405 super.onActivityCreated(savedInstanceState); 406 407 if (!mShowsDialog) { 408 return; 409 } 410 411 View view = getView(); 412 if (view != null) { 413 if (view.getParent() != null) { 414 throw new IllegalStateException("DialogFragment can not be attached to a container view"); 415 } 416 mDialog.setContentView(view); 417 } 418 mDialog.setOwnerActivity(getActivity()); 419 mDialog.setCancelable(mCancelable); 420 mDialog.setOnCancelListener(this); 421 mDialog.setOnDismissListener(this); 422 if (savedInstanceState != null) { 423 Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG); 424 if (dialogState != null) { 425 mDialog.onRestoreInstanceState(dialogState); 426 } 427 } 428 } 429 430 @Override 431 public void onStart() { 432 super.onStart(); 433 if (mDialog != null) { 434 mRemoved = false; 435 mDialog.show(); 436 } 437 } 438 439 @Override 440 public void onSaveInstanceState(Bundle outState) { 441 super.onSaveInstanceState(outState); 442 if (mDialog != null) { 443 Bundle dialogState = mDialog.onSaveInstanceState(); 444 if (dialogState != null) { 445 outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState); 446 } 447 } 448 if (mStyle != STYLE_NORMAL) { 449 outState.putInt(SAVED_STYLE, mStyle); 450 } 451 if (mTheme != 0) { 452 outState.putInt(SAVED_THEME, mTheme); 453 } 454 if (!mCancelable) { 455 outState.putBoolean(SAVED_CANCELABLE, mCancelable); 456 } 457 if (!mShowsDialog) { 458 outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); 459 } 460 if (mBackStackId != -1) { 461 outState.putInt(SAVED_BACK_STACK_ID, mBackStackId); 462 } 463 } 464 465 @Override 466 public void onStop() { 467 super.onStop(); 468 if (mDialog != null) { 469 mDialog.hide(); 470 } 471 } 472 473 /** 474 * Remove dialog. 475 */ 476 @Override 477 public void onDestroyView() { 478 super.onDestroyView(); 479 mDestroyed = true; 480 if (mDialog != null) { 481 // Set removed here because this dismissal is just to hide 482 // the dialog -- we don't want this to cause the fragment to 483 // actually be removed. 484 mRemoved = true; 485 mDialog.dismiss(); 486 mDialog = null; 487 } 488 } 489} 490