811a5a4a |
1 | /* |
2 | * Copyright (C) 2011 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 | |
17 | package android.support.v2.app; |
18 | |
19 | import android.app.Dialog; |
20 | import android.content.Context; |
21 | import android.content.DialogInterface; |
22 | import android.os.Bundle; |
23 | import android.view.LayoutInflater; |
24 | import android.view.View; |
25 | import android.view.ViewGroup; |
26 | import android.view.Window; |
27 | import android.view.WindowManager; |
28 | |
29 | /** |
30 | * Static library support version of the framework's {@link android.app.DialogFragment}. |
31 | * Used to write apps that run on platforms prior to Android 3.0. When running |
32 | * on Android 3.0 or above, this implementation is still used; it does not try |
33 | * to switch to the framework's implementation. See the framework SDK |
34 | * documentation for a class overview. |
35 | */ |
36 | public class DialogFragment extends Fragment |
37 | implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { |
38 | |
39 | /** |
40 | * Style for {@link #setStyle(int, int)}: a basic, |
41 | * normal dialog. |
42 | */ |
43 | public static final int STYLE_NORMAL = 0; |
44 | |
45 | /** |
46 | * Style for {@link #setStyle(int, int)}: don't include |
47 | * a title area. |
48 | */ |
49 | public static final int STYLE_NO_TITLE = 1; |
50 | |
51 | /** |
52 | * Style for {@link #setStyle(int, int)}: don't draw |
53 | * any frame at all; the view hierarchy returned by {@link #onCreateView} |
54 | * is entirely responsible for drawing the dialog. |
55 | */ |
56 | public static final int STYLE_NO_FRAME = 2; |
57 | |
58 | /** |
59 | * Style for {@link #setStyle(int, int)}: like |
60 | * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog. |
61 | * The user can not touch it, and its window will not receive input focus. |
62 | */ |
63 | public static final int STYLE_NO_INPUT = 3; |
64 | |
65 | private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState"; |
66 | private static final String SAVED_STYLE = "android:style"; |
67 | private static final String SAVED_THEME = "android:theme"; |
68 | private static final String SAVED_CANCELABLE = "android:cancelable"; |
69 | private static final String SAVED_SHOWS_DIALOG = "android:showsDialog"; |
70 | private static final String SAVED_BACK_STACK_ID = "android:backStackId"; |
71 | |
72 | int mStyle = STYLE_NORMAL; |
73 | int mTheme = 0; |
74 | boolean mCancelable = true; |
75 | boolean mShowsDialog = true; |
76 | int mBackStackId = -1; |
77 | |
78 | Dialog mDialog; |
79 | boolean mDestroyed; |
80 | boolean mRemoved; |
81 | |
82 | public DialogFragment() { |
83 | } |
84 | |
85 | /** |
86 | * Call to customize the basic appearance and behavior of the |
87 | * fragment's dialog. This can be used for some common dialog behaviors, |
88 | * taking care of selecting flags, theme, and other options for you. The |
89 | * same effect can be achieve by manually setting Dialog and Window |
90 | * attributes yourself. Calling this after the fragment's Dialog is |
91 | * created will have no effect. |
92 | * |
93 | * @param style Selects a standard style: may be {@link #STYLE_NORMAL}, |
94 | * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or |
95 | * {@link #STYLE_NO_INPUT}. |
96 | * @param theme Optional custom theme. If 0, an appropriate theme (based |
97 | * on the style) will be selected for you. |
98 | */ |
99 | public void setStyle(int style, int theme) { |
100 | mStyle = style; |
101 | if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { |
102 | mTheme = android.R.style.Theme_Panel; |
103 | } |
104 | if (theme != 0) { |
105 | mTheme = theme; |
106 | } |
107 | } |
108 | |
109 | /** |
110 | * Display the dialog, adding the fragment to the given FragmentManager. This |
111 | * is a convenience for explicitly creating a transaction, adding the |
112 | * fragment to it with the given tag, and committing it. This does |
113 | * <em>not</em> add the transaction to the back stack. When the fragment |
114 | * is dismissed, a new transaction will be executed to remove it from |
115 | * the activity. |
116 | * @param manager The FragmentManager this fragment will be added to. |
117 | * @param tag The tag for this fragment, as per |
118 | * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. |
119 | */ |
120 | public void show(FragmentManager manager, String tag) { |
121 | FragmentTransaction ft = manager.beginTransaction(); |
122 | ft.add(this, tag); |
123 | ft.commit(); |
124 | } |
125 | |
126 | /** |
127 | * Display the dialog, adding the fragment using an existing transaction |
128 | * and then committing the transaction. |
129 | * @param transaction An existing transaction in which to add the fragment. |
130 | * @param tag The tag for this fragment, as per |
131 | * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. |
132 | * @return Returns the identifier of the committed transaction, as per |
133 | * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. |
134 | */ |
135 | public int show(FragmentTransaction transaction, String tag) { |
136 | transaction.add(this, tag); |
137 | mRemoved = false; |
138 | mBackStackId = transaction.commit(); |
139 | return mBackStackId; |
140 | } |
141 | |
142 | /** |
143 | * Dismiss the fragment and its dialog. If the fragment was added to the |
144 | * back stack, all back stack state up to and including this entry will |
145 | * be popped. Otherwise, a new transaction will be committed to remove |
146 | * the fragment. |
147 | */ |
148 | public void dismiss() { |
149 | dismissInternal(false); |
150 | } |
151 | |
152 | void dismissInternal(boolean allowStateLoss) { |
153 | if (mDialog != null) { |
154 | mDialog.dismiss(); |
155 | mDialog = null; |
156 | } |
157 | mRemoved = true; |
158 | if (mBackStackId >= 0) { |
159 | getFragmentManager().popBackStack(mBackStackId, |
160 | FragmentManager.POP_BACK_STACK_INCLUSIVE); |
161 | mBackStackId = -1; |
162 | } else { |
163 | FragmentTransaction ft = getFragmentManager().beginTransaction(); |
164 | ft.remove(this); |
165 | if (allowStateLoss) { |
166 | ft.commitAllowingStateLoss(); |
167 | } else { |
168 | ft.commit(); |
169 | } |
170 | } |
171 | } |
172 | |
173 | public Dialog getDialog() { |
174 | return mDialog; |
175 | } |
176 | |
177 | public int getTheme() { |
178 | return mTheme; |
179 | } |
180 | |
181 | /** |
182 | * Control whether the shown Dialog is cancelable. Use this instead of |
183 | * directly calling {@link Dialog#setCancelable(boolean) |
184 | * Dialog.setCancelable(boolean)}, because DialogFragment needs to change |
185 | * its behavior based on this. |
186 | * |
187 | * @param cancelable If true, the dialog is cancelable. The default |
188 | * is true. |
189 | */ |
190 | public void setCancelable(boolean cancelable) { |
191 | mCancelable = cancelable; |
192 | if (mDialog != null) mDialog.setCancelable(cancelable); |
193 | } |
194 | |
195 | /** |
196 | * Return the current value of {@link #setCancelable(boolean)}. |
197 | */ |
198 | public boolean isCancelable() { |
199 | return mCancelable; |
200 | } |
201 | |
202 | /** |
203 | * Controls whether this fragment should be shown in a dialog. If not |
204 | * set, no Dialog will be created in {@link #onActivityCreated(Bundle)}, |
205 | * and the fragment's view hierarchy will thus not be added to it. This |
206 | * allows you to instead use it as a normal fragment (embedded inside of |
207 | * its activity). |
208 | * |
209 | * <p>This is normally set for you based on whether the fragment is |
210 | * associated with a container view ID passed to |
211 | * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}. |
212 | * If the fragment was added with a container, setShowsDialog will be |
213 | * initialized to false; otherwise, it will be true. |
214 | * |
215 | * @param showsDialog If true, the fragment will be displayed in a Dialog. |
216 | * If false, no Dialog will be created and the fragment's view hierarchly |
217 | * left undisturbed. |
218 | */ |
219 | public void setShowsDialog(boolean showsDialog) { |
220 | mShowsDialog = showsDialog; |
221 | } |
222 | |
223 | /** |
224 | * Return the current value of {@link #setShowsDialog(boolean)}. |
225 | */ |
226 | public boolean getShowsDialog() { |
227 | return mShowsDialog; |
228 | } |
229 | |
230 | @Override |
231 | public void onCreate(Bundle savedInstanceState) { |
232 | super.onCreate(savedInstanceState); |
233 | |
234 | mShowsDialog = mContainerId == 0; |
235 | |
236 | if (savedInstanceState != null) { |
237 | mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL); |
238 | mTheme = savedInstanceState.getInt(SAVED_THEME, 0); |
239 | mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true); |
240 | mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); |
241 | mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1); |
242 | } |
243 | |
244 | } |
245 | |
246 | /** @hide */ |
247 | @Override |
248 | public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { |
249 | if (!mShowsDialog) { |
250 | return super.getLayoutInflater(savedInstanceState); |
251 | } |
252 | |
253 | mDialog = onCreateDialog(savedInstanceState); |
254 | mDestroyed = false; |
255 | switch (mStyle) { |
256 | case STYLE_NO_INPUT: |
257 | mDialog.getWindow().addFlags( |
258 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | |
259 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); |
260 | // fall through... |
261 | case STYLE_NO_FRAME: |
262 | case STYLE_NO_TITLE: |
263 | mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); |
264 | } |
265 | return (LayoutInflater)mDialog.getContext().getSystemService( |
266 | Context.LAYOUT_INFLATER_SERVICE); |
267 | } |
268 | |
269 | /** |
270 | * Override to build your own custom Dialog container. This is typically |
271 | * used to show an AlertDialog instead of a generic Dialog; when doing so, |
272 | * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need |
273 | * to be implemented since the AlertDialog takes care of its own content. |
274 | * |
275 | * <p>This method will be called after {@link #onCreate(Bundle)} and |
276 | * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. The |
277 | * default implementation simply instantiates and returns a {@link Dialog} |
278 | * class. |
279 | * |
280 | * <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener |
281 | * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener |
282 | * Dialog.setOnDismissListener} callbacks. You must not set them yourself.</em> |
283 | * To find out about these events, override {@link #onCancel(DialogInterface)} |
284 | * and {@link #onDismiss(DialogInterface)}.</p> |
285 | * |
286 | * @param savedInstanceState The last saved instance state of the Fragment, |
287 | * or null if this is a freshly created Fragment. |
288 | * |
289 | * @return Return a new Dialog instance to be displayed by the Fragment. |
290 | */ |
291 | public Dialog onCreateDialog(Bundle savedInstanceState) { |
292 | return new Dialog(getActivity(), getTheme()); |
293 | } |
294 | |
295 | public void onCancel(DialogInterface dialog) { |
296 | } |
297 | |
298 | public void onDismiss(DialogInterface dialog) { |
299 | if (!mRemoved) { |
300 | // Note: we need to use allowStateLoss, because the dialog |
301 | // dispatches this asynchronously so we can receive the call |
302 | // after the activity is paused. Worst case, when the user comes |
303 | // back to the activity they see the dialog again. |
304 | dismissInternal(true); |
305 | } |
306 | } |
307 | |
308 | @Override |
309 | public void onActivityCreated(Bundle savedInstanceState) { |
310 | super.onActivityCreated(savedInstanceState); |
311 | |
312 | if (!mShowsDialog) { |
313 | return; |
314 | } |
315 | |
316 | View view = getView(); |
317 | if (view != null) { |
318 | if (view.getParent() != null) { |
319 | throw new IllegalStateException("DialogFragment can not be attached to a container view"); |
320 | } |
321 | mDialog.setContentView(view); |
322 | } |
323 | mDialog.setOwnerActivity(getActivity()); |
324 | mDialog.setCancelable(mCancelable); |
325 | mDialog.setOnCancelListener(this); |
326 | mDialog.setOnDismissListener(this); |
327 | if (savedInstanceState != null) { |
328 | Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG); |
329 | if (dialogState != null) { |
330 | mDialog.onRestoreInstanceState(dialogState); |
331 | } |
332 | } |
333 | } |
334 | |
335 | @Override |
336 | public void onStart() { |
337 | super.onStart(); |
338 | if (mDialog != null) { |
339 | mRemoved = false; |
340 | mDialog.show(); |
341 | } |
342 | } |
343 | |
344 | @Override |
345 | public void onSaveInstanceState(Bundle outState) { |
346 | super.onSaveInstanceState(outState); |
347 | if (mDialog != null) { |
348 | Bundle dialogState = mDialog.onSaveInstanceState(); |
349 | if (dialogState != null) { |
350 | outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState); |
351 | } |
352 | } |
353 | if (mStyle != STYLE_NORMAL) { |
354 | outState.putInt(SAVED_STYLE, mStyle); |
355 | } |
356 | if (mTheme != 0) { |
357 | outState.putInt(SAVED_THEME, mTheme); |
358 | } |
359 | if (!mCancelable) { |
360 | outState.putBoolean(SAVED_CANCELABLE, mCancelable); |
361 | } |
362 | if (!mShowsDialog) { |
363 | outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); |
364 | } |
365 | if (mBackStackId != -1) { |
366 | outState.putInt(SAVED_BACK_STACK_ID, mBackStackId); |
367 | } |
368 | } |
369 | |
370 | @Override |
371 | public void onStop() { |
372 | super.onStop(); |
373 | if (mDialog != null) { |
374 | mDialog.hide(); |
375 | } |
376 | } |
377 | |
378 | /** |
379 | * Remove dialog. |
380 | */ |
381 | @Override |
382 | public void onDestroyView() { |
383 | super.onDestroyView(); |
384 | mDestroyed = true; |
385 | if (mDialog != null) { |
386 | // Set removed here because this dismissal is just to hide |
387 | // the dialog -- we don't want this to cause the fragment to |
388 | // actually be removed. |
389 | mRemoved = true; |
390 | mDialog.dismiss(); |
391 | mDialog = null; |
392 | } |
393 | } |
394 | } |