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