DialogFragment.java revision 727782053ced0cac5beadc2c7ee9382d0f1ba1f5
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 = android.R.style.Theme_Dialog_NoFrame;
203        }
204        if (theme != 0) {
205            mTheme = theme;
206        }
207    }
208
209    /**
210     * @deprecated Please use {@link #show(FragmentManager, String)}.
211     */
212    @Deprecated
213    public void show(Activity activity, String tag) {
214        FragmentTransaction ft = activity.openFragmentTransaction();
215        ft.add(this, tag);
216        ft.commit();
217    }
218
219    /**
220     * Display the dialog, adding the fragment to the given FragmentManager.  This
221     * is a convenience for explicitly creating a transaction, adding the
222     * fragment to it with the given tag, and committing it.  This does
223     * <em>not</em> add the transaction to the back stack.  When the fragment
224     * is dismissed, a new transaction will be executed to remove it from
225     * the activity.
226     * @param manager The FragmentManager this fragment will be added to.
227     * @param tag The tag for this fragment, as per
228     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
229     */
230    public void show(FragmentManager manager, String tag) {
231        FragmentTransaction ft = manager.openTransaction();
232        ft.add(this, tag);
233        ft.commit();
234    }
235
236    /**
237     * Display the dialog, adding the fragment using an existing transaction
238     * and then committing the transaction.
239     * @param transaction An existing transaction in which to add the fragment.
240     * @param tag The tag for this fragment, as per
241     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
242     * @return Returns the identifier of the committed transaction, as per
243     * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
244     */
245    public int show(FragmentTransaction transaction, String tag) {
246        transaction.add(this, tag);
247        mRemoved = false;
248        mBackStackId = transaction.commit();
249        return mBackStackId;
250    }
251
252    /**
253     * Dismiss the fragment and its dialog.  If the fragment was added to the
254     * back stack, all back stack state up to and including this entry will
255     * be popped.  Otherwise, a new transaction will be committed to remove
256     * the fragment.
257     */
258    public void dismiss() {
259        if (mDialog != null) {
260            mDialog.dismiss();
261            mDialog = null;
262        }
263        mRemoved = true;
264        if (mBackStackId >= 0) {
265            getFragmentManager().popBackStack(mBackStackId,
266                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
267            mBackStackId = -1;
268        } else {
269            FragmentTransaction ft = getFragmentManager().openTransaction();
270            ft.remove(this);
271            ft.commit();
272        }
273    }
274
275    public Dialog getDialog() {
276        return mDialog;
277    }
278
279    public int getTheme() {
280        return mTheme;
281    }
282
283    /**
284     * Control whether the shown Dialog is cancelable.  Use this instead of
285     * directly calling {@link Dialog#setCancelable(boolean)
286     * Dialog.setCancelable(boolean)}, because DialogFragment needs to change
287     * its behavior based on this.
288     *
289     * @param cancelable If true, the dialog is cancelable.  The default
290     * is true.
291     */
292    public void setCancelable(boolean cancelable) {
293        mCancelable = cancelable;
294        if (mDialog != null) mDialog.setCancelable(cancelable);
295    }
296
297    /**
298     * Return the current value of {@link #setCancelable(boolean)}.
299     */
300    public boolean getCancelable() {
301        return mCancelable;
302    }
303
304    /**
305     * Controls whether this fragment should be shown in a dialog.  If not
306     * set, no Dialog will be created in {@link #onActivityCreated(Bundle)},
307     * and the fragment's view hierarchy will thus not be added to it.  This
308     * allows you to instead use it as a normal fragment (embedded inside of
309     * its activity).
310     *
311     * <p>This is normally set for you based on whether the fragment is
312     * associated with a container view ID passed to
313     * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}.
314     * If the fragment was added with a container, setShowsDialog will be
315     * initialized to false; otherwise, it will be true.
316     *
317     * @param showsDialog If true, the fragment will be displayed in a Dialog.
318     * If false, no Dialog will be created and the fragment's view hierarchly
319     * left undisturbed.
320     */
321    public void setShowsDialog(boolean showsDialog) {
322        mShowsDialog = showsDialog;
323    }
324
325    /**
326     * Return the current value of {@link #setShowsDialog(boolean)}.
327     */
328    public boolean getShowsDialog() {
329        return mShowsDialog;
330    }
331
332    @Override
333    public void onCreate(Bundle savedInstanceState) {
334        super.onCreate(savedInstanceState);
335
336        mShowsDialog = mContainerId == 0;
337
338        if (savedInstanceState != null) {
339            mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
340            mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
341            mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
342            mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
343            mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
344        }
345    }
346
347    public Dialog onCreateDialog(Bundle savedInstanceState) {
348        return new Dialog(getActivity(), getTheme());
349    }
350
351    public void onCancel(DialogInterface dialog) {
352    }
353
354    public void onDismiss(DialogInterface dialog) {
355        if (!mRemoved) {
356            dismiss();
357        }
358    }
359
360    @Override
361    public void onActivityCreated(Bundle savedInstanceState) {
362        super.onActivityCreated(savedInstanceState);
363
364        if (!mShowsDialog) {
365            return;
366        }
367
368        mDialog = onCreateDialog(savedInstanceState);
369        mDestroyed = false;
370        switch (mStyle) {
371            case STYLE_NO_INPUT:
372                mDialog.getWindow().addFlags(
373                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
374                        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
375                // fall through...
376            case STYLE_NO_FRAME:
377            case STYLE_NO_TITLE:
378                mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
379        }
380        View view = getView();
381        if (view != null) {
382            if (view.getParent() != null) {
383                throw new IllegalStateException("DialogFragment can not be attached to a container view");
384            }
385            mDialog.setContentView(view);
386        }
387        mDialog.setOwnerActivity(getActivity());
388        mDialog.setCancelable(mCancelable);
389        mDialog.setOnCancelListener(this);
390        mDialog.setOnDismissListener(this);
391        if (savedInstanceState != null) {
392            Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
393            if (dialogState != null) {
394                mDialog.onRestoreInstanceState(dialogState);
395            }
396        }
397    }
398
399    @Override
400    public void onStart() {
401        super.onStart();
402        if (mDialog != null) {
403            mRemoved = false;
404            mDialog.show();
405        }
406    }
407
408    @Override
409    public void onSaveInstanceState(Bundle outState) {
410        super.onSaveInstanceState(outState);
411        if (mDialog != null) {
412            Bundle dialogState = mDialog.onSaveInstanceState();
413            if (dialogState != null) {
414                outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
415            }
416        }
417        if (mStyle != STYLE_NORMAL) {
418            outState.putInt(SAVED_STYLE, mStyle);
419        }
420        if (mTheme != 0) {
421            outState.putInt(SAVED_THEME, mTheme);
422        }
423        if (!mCancelable) {
424            outState.putBoolean(SAVED_CANCELABLE, mCancelable);
425        }
426        if (!mShowsDialog) {
427            outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
428        }
429        if (mBackStackId != -1) {
430            outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
431        }
432    }
433
434    @Override
435    public void onStop() {
436        super.onStop();
437        if (mDialog != null) {
438            mDialog.hide();
439        }
440    }
441
442    /**
443     * Remove dialog.
444     */
445    @Override
446    public void onDestroyView() {
447        super.onDestroyView();
448        mDestroyed = true;
449        if (mDialog != null) {
450            // Set removed here because this dismissal is just to hide
451            // the dialog -- we don't want this to cause the fragment to
452            // actually be removed.
453            mRemoved = true;
454            mDialog.dismiss();
455            mDialog = null;
456        }
457    }
458}
459