DialogFragment.java revision b7a2e4772220c4b41df1260cedaf8912f4b07547
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 */
40public class DialogFragment extends Fragment
41        implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
42
43    /**
44     * Style for {@link #DialogFragment(int, int)} constructor: a basic,
45     * normal dialog.
46     */
47    public static final int STYLE_NORMAL = 0;
48
49    /**
50     * Style for {@link #DialogFragment(int, int)} constructor: don't include
51     * a title area.
52     */
53    public static final int STYLE_NO_TITLE = 1;
54
55    /**
56     * Style for {@link #DialogFragment(int, int)} constructor: don't draw
57     * any frame at all; the view hierarchy returned by {@link #onCreateView}
58     * is entirely responsible for drawing the dialog.
59     */
60    public static final int STYLE_NO_FRAME = 2;
61
62    /**
63     * Style for {@link #DialogFragment(int, int)} constructor: like
64     * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog.
65     * The user can not touch it, and its window will not receive input focus.
66     */
67    public static final int STYLE_NO_INPUT = 3;
68
69    private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState";
70    private static final String SAVED_STYLE = "android:style";
71    private static final String SAVED_THEME = "android:theme";
72    private static final String SAVED_CANCELABLE = "android:cancelable";
73    private static final String SAVED_BACK_STACK_ID = "android:backStackId";
74
75    int mStyle = STYLE_NORMAL;
76    int mTheme = 0;
77    boolean mCancelable = true;
78    int mBackStackId = -1;
79
80    Dialog mDialog;
81    boolean mDestroyed;
82
83    public DialogFragment() {
84    }
85
86    /**
87     * Call to customize the basic appearance and behavior of the
88     * fragment's dialog.  This can be used for some common dialog behaviors,
89     * taking care of selecting flags, theme, and other options for you.  The
90     * same effect can be achieve by manually setting Dialog and Window
91     * attributes yourself.  Calling this after the fragment's Dialog is
92     * created will have no effect.
93     *
94     * @param style Selects a standard style: may be {@link #STYLE_NORMAL},
95     * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or
96     * {@link #STYLE_NO_INPUT}.
97     * @param theme Optional custom theme.  If 0, an appropriate theme (based
98     * on the style) will be selected for you.
99     */
100    public void setStyle(int style, int theme) {
101        mStyle = style;
102        if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
103            mTheme = android.R.style.Theme_Dialog_NoFrame;
104        }
105        if (theme != 0) {
106            mTheme = theme;
107        }
108    }
109
110    /**
111     * Display the dialog, adding the fragment to the given activity.  This
112     * is a convenience for explicitly creating a transaction, adding the
113     * fragment to it with the given tag, and committing it.  This does
114     * <em>not</em> add the transaction to the back stack.  When the fragment
115     * is dismissed, a new transaction will be executed to remove it from
116     * the activity.
117     * @param activity The activity this fragment will be added to.
118     * @param tag The tag for this fragment, as per
119     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
120     */
121    public void show(Activity activity, String tag) {
122        FragmentTransaction ft = activity.openFragmentTransaction();
123        ft.add(this, tag);
124        ft.commit();
125    }
126
127    /**
128     * Display the dialog, adding the fragment to the given activity using
129     * an existing transaction and then committing the transaction.
130     * @param activity The activity this fragment will be added to.
131     * @param transaction An existing transaction in which to add the fragment.
132     * @param tag The tag for this fragment, as per
133     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
134     * @return Returns the identifier of the committed transaction, as per
135     * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
136     */
137    public int show(Activity activity, FragmentTransaction transaction, String tag) {
138        transaction.add(this, tag);
139        mBackStackId = transaction.commit();
140        return mBackStackId;
141    }
142
143    /**
144     * Dismiss the fragment and its dialog.  If the fragment was added to the
145     * back stack, all back stack state up to and including this entry will
146     * be popped.  Otherwise, a new transaction will be committed to remove
147     * the fragment.
148     */
149    public void dismiss() {
150        if (mDialog != null) {
151            mDialog.dismiss();
152        }
153        if (mBackStackId >= 0) {
154            getActivity().popBackStack(mBackStackId, Activity.POP_BACK_STACK_INCLUSIVE);
155            mBackStackId = -1;
156        } else {
157            FragmentTransaction ft = getActivity().openFragmentTransaction();
158            ft.remove(this);
159            ft.commit();
160        }
161    }
162
163    public Dialog getDialog() {
164        return mDialog;
165    }
166
167    public int getTheme() {
168        return mTheme;
169    }
170
171    public void setCancelable(boolean cancelable) {
172        mCancelable = cancelable;
173        if (mDialog != null) mDialog.setCancelable(cancelable);
174    }
175
176    public boolean getCancelable() {
177        return mCancelable;
178    }
179
180    @Override
181    public void onCreate(Bundle savedInstanceState) {
182        super.onCreate(savedInstanceState);
183        if (savedInstanceState != null) {
184            mStyle = savedInstanceState.getInt(SAVED_STYLE, mStyle);
185            mTheme = savedInstanceState.getInt(SAVED_THEME, mTheme);
186            mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, mCancelable);
187            mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, mBackStackId);
188        }
189    }
190
191    public Dialog onCreateDialog(Bundle savedInstanceState) {
192        return new Dialog(getActivity(), getTheme());
193    }
194
195    public void onCancel(DialogInterface dialog) {
196        if (mBackStackId >= 0) {
197            // If this fragment is part of the back stack, then cancelling
198            // the dialog means popping off the back stack.
199            getActivity().popBackStack(mBackStackId, Activity.POP_BACK_STACK_INCLUSIVE);
200            mBackStackId = -1;
201        }
202    }
203
204    public void onDismiss(DialogInterface dialog) {
205    }
206
207    @Override
208    public void onActivityCreated(Bundle savedInstanceState) {
209        super.onActivityCreated(savedInstanceState);
210        mDialog = onCreateDialog(savedInstanceState);
211        mDestroyed = false;
212        switch (mStyle) {
213            case STYLE_NO_INPUT:
214                mDialog.getWindow().addFlags(
215                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
216                        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
217                // fall through...
218            case STYLE_NO_FRAME:
219            case STYLE_NO_TITLE:
220                mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
221        }
222        View view = getView();
223        if (view != null) {
224            if (view.getParent() != null) {
225                throw new IllegalStateException("DialogFragment can not be attached to a container view");
226            }
227            mDialog.setContentView(view);
228        }
229        mDialog.setOwnerActivity(getActivity());
230        mDialog.setCancelable(mCancelable);
231        mDialog.setOnCancelListener(this);
232        mDialog.setOnDismissListener(this);
233        if (savedInstanceState != null) {
234            Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
235            if (dialogState != null) {
236                mDialog.onRestoreInstanceState(dialogState);
237            }
238        }
239    }
240
241    @Override
242    public void onStart() {
243        super.onStart();
244        mDialog.show();
245    }
246
247    @Override
248    public void onSaveInstanceState(Bundle outState) {
249        super.onSaveInstanceState(outState);
250        if (mDialog != null) {
251            Bundle dialogState = mDialog.onSaveInstanceState();
252            if (dialogState != null) {
253                outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
254            }
255        }
256        outState.putInt(SAVED_STYLE, mStyle);
257        outState.putInt(SAVED_THEME, mTheme);
258        outState.putBoolean(SAVED_CANCELABLE, mCancelable);
259        outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
260    }
261
262    @Override
263    public void onStop() {
264        super.onStop();
265        mDialog.hide();
266    }
267
268    /**
269     * Detach from list view.
270     */
271    @Override
272    public void onDestroyView() {
273        super.onDestroyView();
274        mDestroyed = true;
275        mDialog.dismiss();
276        mDialog = null;
277    }
278}
279