Java tutorial
/* * Copyright 2014 wada811 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package at.wada811.android.dialogfragments; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.os.Build; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.util.TypedValue; import android.view.View; import android.widget.ListAdapter; import at.wada811.android.dialogfragments.interfaces.DialogFragmentCallback; import at.wada811.android.dialogfragments.interfaces.DialogFragmentCallbackProvider; import at.wada811.android.dialogfragments.interfaces.DialogFragmentInterface; @SuppressLint("InlinedApi") public class AlertDialogFragment extends AbstractDialogFragment implements DialogFragmentInterface { final AlertDialogFragment self = this; public static final String TAG = AlertDialogFragment.class.getSimpleName(); /** * Special theme constant for {@link AlertDialogFragment.Builder#Builder(android.content.Context, int)}: use * the traditional (pre-Holo) alert dialog theme. */ public static final int THEME_TRADITIONAL = AlertDialog.THEME_TRADITIONAL; /** * Special theme constant for {@link AlertDialogFragment.Builder#Builder(android.content.Context, int)}: use * the holographic alert theme with a dark background. */ public static final int THEME_HOLO_DARK = AlertDialog.THEME_HOLO_DARK; /** * Special theme constant for {@link AlertDialogFragment.Builder#Builder(android.content.Context, int)}: use * the holographic alert theme with a light background. */ public static final int THEME_HOLO_LIGHT = AlertDialog.THEME_HOLO_LIGHT; /** * Special theme constant for {@link AlertDialogFragment.Builder#Builder(android.content.Context, int)}: use * the device's default alert theme with a dark background. */ public static final int THEME_DEVICE_DEFAULT_DARK = AlertDialog.THEME_DEVICE_DEFAULT_DARK; /** * Special theme constant for {@link AlertDialogFragment.Builder#Builder(android.content.Context, int)}: use * the device's default alert theme with a dark background. */ public static final int THEME_DEVICE_DEFAULT_LIGHT = AlertDialog.THEME_DEVICE_DEFAULT_LIGHT; protected static final String THEME = "theme"; private static final String INVERSE_BACKGROUND = "inverseBackground"; private static final String CUSTOM_TITLE = "customTitle"; protected static final String MESSAGE = "message"; private static final String VIEW = "view"; private static final String ITEMS = "items"; private static final String ADAPTER = "adapter"; private static final String CHECKED_ITEMS = "checkedItems"; private static final String MULTI_CHOICE_ITEMS = "multiChoiceItems"; private static final String CHECKED_ITEM = "checkedItem"; private static final String SINGLE_CHOICE_ITEMS = "singleChoiceItems"; private static final String POSITIVE_BUTTON = "positiveButton"; private static final String NEUTRAL_BUTTON = "neutralButton"; private static final String NEGATIVE_BUTTON = "negativeButton"; private static final String POSITIVE_CLICK_LISTENER = "onClickPositive"; private static final String NEUTRAL_CLICK_LISTENER = "onClickNeutral"; private static final String NEGATIVE_CLICK_LISTENER = "onClickNegative"; public static class Builder { private final Context context; private final Bundle args = new Bundle(); private boolean isCancelable = true; private boolean isCanceledOnTouchOutside = true; private boolean useOnShowListener; private boolean useOnCancelListener; private boolean useOnDismissListener; private boolean useOnKeyListener; private Bundle extra; /** * Constructor using a context for this builder and the {@link AlertDialogFragment} it * creates. */ public Builder(Context context) { this(context, 0); } /** * Constructor using a context and theme for this builder and * the {@link AlertDialogFragment} it creates. The actual theme * that an AlertDialog uses is a private implementation, however you can * here supply either the name of an attribute in the theme from which * to get the dialog's style (such as {@link android.R.attr#alertDialogTheme} or one of the * constants {@link AlertDialogFragment#THEME_TRADITIONAL}, * {@link AlertDialogFragment#THEME_HOLO_DARK}, * or {@link AlertDialogFragment#THEME_HOLO_LIGHT}. */ public Builder(Context context, int theme) { this.context = context.getApplicationContext(); args.putInt(THEME, theme); } /** * Creates a {@link AlertDialogFragment} with the arguments supplied to this builder. * It does not {@link AlertDialogFragment#show} the dialog. * This allows the user to do any extra processing before displaying the dialog. * Use {@link #show} if you don't have any other processing * to do and want this to be created and displayed. */ public AlertDialogFragment create() { final AlertDialogFragment fragment = new AlertDialogFragment(); fragment.setFromBuilder(true); fragment.setArguments(args); if (!isCancelable) { fragment.setCancelable(false); } if (!isCanceledOnTouchOutside) { fragment.setCanceledOnTouchOutside(false); } fragment.useOnShowListener = useOnShowListener; fragment.useOnCancelListener = useOnCancelListener; fragment.useOnDismissListener = useOnDismissListener; fragment.useOnKeyListener = useOnKeyListener; if (extra != null) { fragment.setExtra(extra); } return fragment; } /** * Creates a {@link AlertDialogFragment} with the arguments supplied to this builder and * {@link AlertDialogFragment#show}'s the dialog. */ public void show(FragmentManager manager, String tag) { create().show(manager, tag); } /** * Creates a {@link AlertDialogFragment} with the arguments supplied to this builder and * {@link AlertDialogFragment#show}'s the dialog. */ public void show(FragmentTransaction transaction, String tag) { create().show(transaction, tag); } /** * Sets whether the dialog is cancelable or not. Default is true. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setCancelable(boolean cancelable) { isCancelable = cancelable; return this; } /** * Sets whether this dialog is canceled when touched outside the window's bounds. * If setting to true, the dialog is set to be cancelable if not already set. * * @param cancel Whether the dialog should be canceled when touched outside the window. */ public Builder setCanceledOnTouchOutside(boolean cancel) { isCanceledOnTouchOutside = cancel; return this; } /** * Sets the Dialog to use the inverse background, regardless of what the * contents is. * * @param useInverseBackground Whether to use the inverse background * @return This Builder object to allow for chaining of calls to set methods */ public Builder setInverseBackgroundForced(boolean useInverseBackground) { args.putInt(INVERSE_BACKGROUND, useInverseBackground ? VALUE_TRUE : VALUE_FALSE); return this; } /** * Set the resource id of the {@link android.graphics.drawable.Drawable} to be used in the * title. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setIcon(int iconId) { args.putInt(ICON_ID, iconId); return this; } /** * Set an icon as supplied by a theme attribute. e.g. android.R.attr.alertDialogIcon * * @param attrId ID of a theme attribute that points to a drawable resource. */ public Builder setIconAttribute(int attrId) { TypedValue out = new TypedValue(); context.getTheme().resolveAttribute(attrId, out, true); setIcon(out.resourceId); return this; } /** * Sets an extra object. * * @return This Builder object to allow for chaining of calls to set methods * @see AlertDialogFragment#setExtra(android.os.Bundle) */ public Builder setExtra(Bundle extra) { this.extra = extra; return this; } /** * Set the title displayed in the {@link android.app.Dialog}. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setTitle(CharSequence title) { args.putCharSequence(TITLE, title); return this; } /** * Set the title using the given resource id. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setTitle(int resId) { args.putCharSequence(TITLE, context.getText(resId)); return this; } public Builder setCustomTitle(DialogFragmentCallbackProvider povider) { assertListenerBindable(povider); args.putBoolean(CUSTOM_TITLE, true); return this; } /** * Set the message to display. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setMessage(CharSequence message) { args.putCharSequence(MESSAGE, message); return this; } /** * Set the message to display using the given resource id. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setMessage(int resId) { return setMessage(context.getText(resId)); } public Builder setView(DialogFragmentCallbackProvider povider) { assertListenerBindable(povider); args.putBoolean(VIEW, true); return this; } /** * Set a list of items to be displayed in the dialog as the content, * you will be notified of the selected item via the supplied listener. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setItems(CharSequence[] items) { args.putCharSequenceArray(ITEMS, items); return this; } /** * Set a list of items to be displayed in the dialog as the content, * you will be notified of the selected item via the supplied listener. * This should be an array type i.e. R.array.foo * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setItems(int itemsId) { return setItems(context.getResources().getTextArray(itemsId)); } /** * Set a list of items, which are supplied by the given {@link android.widget.ListAdapter}, to be * displayed in the dialog as the content, * you will be notified of the selected item via the supplied listener. * * @param povider The {@link DialogFragmentCallback#getAdapter(DialogFragmentInterface)} to * supply the list of items * @return This Builder object to allow for chaining of calls to set methods */ public Builder setAdapter(DialogFragmentCallbackProvider povider) { assertListenerBindable(povider); args.putBoolean(ADAPTER, true); return this; } /** * Set a list of items to be displayed in the dialog as the content. * The list will have a check mark displayed to the right of the text for the checked item. * Clicking on an item in the list will not dismiss the dialog. * Clicking on a button will dismiss the dialog. * * @param items the items to be displayed. * @param checkedItem specifies which item is checked. If -1 no items are checked. * @return This Builder object to allow for chaining of calls to set methods */ public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem) { args.putCharSequenceArray(SINGLE_CHOICE_ITEMS, items); args.putInt(CHECKED_ITEM, checkedItem); return this; } /** * Set a list of items to be displayed in the dialog as the content. * This should be an array type i.e. R.array.foo * The list will have a check mark displayed to the right of the text for the checked item. * Clicking on an item in the list will not dismiss the dialog. * Clicking on a button will dismiss the dialog. * * @param itemsId the resource id of an array i.e. R.array.foo * @param checkedItem specifies which item is checked. If -1 no items are checked. * @return This Builder object to allow for chaining of calls to set methods */ public Builder setSingleChoiceItems(int itemsId, int checkedItem) { return setSingleChoiceItems(context.getResources().getTextArray(itemsId), checkedItem); } /** * Set a list of items to be displayed in the dialog as the content. * The list will have a check mark displayed to the right of the text * for each checked item. Clicking on an item in the list will not * dismiss the dialog. Clicking on a button will dismiss the dialog. * * @param items the text of the items to be displayed in the list. * @param checkedItems specifies which items are checked. It should be null in which case no * items are checked. If non null it must be exactly the same length as the array of * items. * @return This Builder object to allow for chaining of calls to set methods */ public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems) { args.putCharSequenceArray(MULTI_CHOICE_ITEMS, items); args.putBooleanArray(CHECKED_ITEMS, checkedItems); return this; } /** * Set a list of items to be displayed in the dialog as the content, * you will be notified of the selected item via the supplied listener. * This should be an array type, e.g. R.array.foo. The list will have * a check mark displayed to the right of the text for each checked * item. Clicking on an item in the list will not dismiss the dialog. * Clicking on a button will dismiss the dialog. * * @param itemsId the resource id of an array i.e. R.array.foo * @param checkedItems specifies which items are checked. It should be null in which case no * items are checked. If non null it must be exactly the same length as the array of * items. * @return This Builder object to allow for chaining of calls to set methods */ public Builder setMultiChoiceItems(int itemsId, boolean[] checkedItems) { return setMultiChoiceItems(context.getResources().getTextArray(itemsId), checkedItems); } /** * Set a listener to be invoked when the positive button of the dialog is pressed. * * @param text The text to display in the positive button * @return This Builder object to allow for chaining of calls to set methods */ public Builder setPositiveButton(CharSequence text, DialogFragmentCallbackProvider provider) { if (provider != null) { assertListenerBindable(provider); args.putBoolean(POSITIVE_CLICK_LISTENER, true); } args.putCharSequence(POSITIVE_BUTTON, text); return this; } /** * Set a listener to be invoked when the positive button of the dialog is pressed. * * @param textId The resource id of the text to display in the positive button * @return This Builder object to allow for chaining of calls to set methods */ public Builder setPositiveButton(int textId, DialogFragmentCallbackProvider provider) { return setPositiveButton(context.getText(textId), provider); } /** * Set a listener to be invoked when the neutral button of the dialog is pressed. * * @param text The text to display in the neutral button * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNeutralButton(CharSequence text, DialogFragmentCallbackProvider provider) { if (provider != null) { assertListenerBindable(provider); args.putBoolean(NEUTRAL_CLICK_LISTENER, true); } args.putCharSequence(NEUTRAL_BUTTON, text); return this; } /** * Set a listener to be invoked when the neutral button of the dialog is pressed. * * @param textId The resource id of the text to display in the neutral button * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNeutralButton(int textId, DialogFragmentCallbackProvider provider) { return setNeutralButton(context.getText(textId), provider); } /** * Set a listener to be invoked when the negative button of the dialog is pressed. * * @param text The text to display in the negative button * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNegativeButton(CharSequence text, DialogFragmentCallbackProvider provider) { if (provider != null) { assertListenerBindable(provider); args.putBoolean(NEGATIVE_CLICK_LISTENER, true); } args.putCharSequence(NEGATIVE_BUTTON, text); return this; } /** * Set a listener to be invoked when the negative button of the dialog is pressed. * * @param textId The resource id of the text to display in the negative button * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNegativeButton(int textId, DialogFragmentCallbackProvider provider) { return setNegativeButton(context.getText(textId), provider); } /** * Sets a listener to be invoked when the dialog is shown. * * @param provider * @return This Builder object to allow for chaining of calls to set methods */ public Builder setOnShowListener(DialogFragmentCallbackProvider provider) { assertListenerBindable(provider); useOnShowListener = true; return this; } /** * Sets the callback that will be called if the dialog is canceled. * * <p> * Even in a cancelable dialog, the dialog may be dismissed for reasons other than being * canceled or one of the supplied choices being selected. If you are interested in * listening for all cases where the dialog is dismissed and not just when it is canceled, * see {@link #setOnDismissListener}. * </p> * * @see #setCancelable(boolean) * @see #setOnDismissListener * * @param provider * @return This Builder object to allow for chaining of calls to set methods */ public Builder setOnCancelListener(DialogFragmentCallbackProvider provider) { assertListenerBindable(provider); useOnCancelListener = true; return this; } /** * Set a listener to be invoked when the dialog is dismissed. * * @param provider * @return This Builder object to allow for chaining of calls to set methods */ public Builder setOnDismissListener(DialogFragmentCallbackProvider provider) { assertListenerBindable(provider); useOnDismissListener = true; return this; } /** * Set a listener to be invoked when the key is pressed. * * @param provider * @return This Builder object to allow for chaining of calls to set methods */ public Builder setOnKeyListener(DialogFragmentCallbackProvider provider) { assertListenerBindable(provider); useOnKeyListener = true; return this; } } private static final String IS_FROM_BUILDER = "isFromBuilder"; boolean isFromBuilder; public AlertDialogFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { return; } isFromBuilder = savedInstanceState.getBoolean(IS_FROM_BUILDER); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { if (!isFromBuilder) { throw new RuntimeException("Use AlertDialogFragment$Builder"); } final Bundle args = getArguments(); final int theme = args.getInt(THEME); AlertDialog.Builder builder; if (theme == VALUE_NULL || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { builder = new AlertDialog.Builder(getActivity()); } else { builder = newDialogBuilder(theme); } iconId = args.getInt(ICON_ID, VALUE_NULL); if (iconId != VALUE_NULL) { builder.setIcon(iconId); } final CharSequence title = args.getCharSequence(TITLE); boolean useCustomTitle = args.getBoolean(CUSTOM_TITLE); if (title != null) { builder.setTitle(title); } else if (useCustomTitle) { setCustomTitle(builder); } final CharSequence message = args.getCharSequence(MESSAGE); if (message != null) { builder.setMessage(message); } final int useInverseBackground = args.getInt(INVERSE_BACKGROUND); if (useInverseBackground != VALUE_NULL) { builder.setInverseBackgroundForced(useInverseBackground == VALUE_TRUE); } // View setView(builder); // List setItems(builder); setAdapter(builder); setSingleChoiceItems(builder); setMultiChoiceItems(builder); // Buttons setPositiveButton(builder); setNeutralButton(builder); setNegativeButton(builder); // Build return builder.create(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(IS_FROM_BUILDER, isFromBuilder); } protected void setFromBuilder(boolean fromBuilder) { isFromBuilder = fromBuilder; } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private AlertDialog.Builder newDialogBuilder(int theme) { return new AlertDialog.Builder(getActivity(), theme); } private void setCustomTitle(AlertDialog.Builder builder) { DialogFragmentCallback listener = getDialogFragmentCallback(); if (listener == null) { throw new RuntimeException("DialogEventListenerPovider has not implemented."); } View view = listener.getCustomTitle(this); if (view == null) { throw new NullPointerException("DialogEventListener#getCustomTitle returns null."); } builder.setCustomTitle(view); } private void setView(AlertDialog.Builder builder) { final Bundle args = getArguments(); boolean useView = args.getBoolean(VIEW, false); if (!useView) { return; } DialogFragmentCallback listener = getDialogFragmentCallback(); if (listener == null) { throw new RuntimeException("DialogEventListenerPovider has not implemented."); } View view = listener.getView(this); if (view == null) { throw new NullPointerException("DialogEventListener#getView returns null."); } builder.setView(view); } private void setItems(AlertDialog.Builder builder) { final Bundle args = getArguments(); final CharSequence[] items = args.getCharSequenceArray(ITEMS); if (items == null) { return; } builder.setItems(items, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { bindItemClickListener(which); } }); } private void setAdapter(AlertDialog.Builder builder) { final Bundle args = getArguments(); boolean useAdapter = args.getBoolean(ADAPTER); if (!useAdapter) { return; } DialogFragmentCallback listener = getDialogFragmentCallback(); if (listener == null) { throw new RuntimeException("DialogEventListenerPovider has not implemented."); } ListAdapter adapter = listener.getAdapter(this); if (adapter == null) { throw new NullPointerException("DialogEventListener#getListAdapter returns null."); } builder.setAdapter(adapter, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { bindItemClickListener(which); } }); } private void setSingleChoiceItems(AlertDialog.Builder builder) { final Bundle args = getArguments(); final CharSequence[] items = args.getCharSequenceArray(SINGLE_CHOICE_ITEMS); final int checkedItem = args.getInt(CHECKED_ITEM); if (items == null) { return; } builder.setSingleChoiceItems(items, checkedItem, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { bindSingleChoiceClickListener(which); } }); } private void setMultiChoiceItems(AlertDialog.Builder builder) { final Bundle args = getArguments(); final CharSequence[] items = args.getCharSequenceArray(MULTI_CHOICE_ITEMS); final boolean[] checked = args.getBooleanArray(CHECKED_ITEMS); if (items == null || checked == null) { return; } if (items.length != checked.length) { throw new IllegalArgumentException("Item's length is not same as checkedItem's length."); } builder.setMultiChoiceItems(items, checked, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { bindMultiChoiceClickListener(which, isChecked); } }); } private void setPositiveButton(AlertDialog.Builder builder) { final Bundle args = getArguments(); final CharSequence positiveButtonText = args.getCharSequence(POSITIVE_BUTTON); if (positiveButtonText == null) { return; } final boolean useOnPositiveClickListener = args.getBoolean(POSITIVE_CLICK_LISTENER); builder.setPositiveButton(positiveButtonText, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (useOnPositiveClickListener) { bindClickListener(which); } } }); } private void setNeutralButton(AlertDialog.Builder builder) { final Bundle args = getArguments(); final CharSequence naturalButtonText = args.getCharSequence(NEUTRAL_BUTTON); if (naturalButtonText == null) { return; } final boolean useOnNeutralClickListener = args.getBoolean(NEUTRAL_CLICK_LISTENER); builder.setNeutralButton(naturalButtonText, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (useOnNeutralClickListener) { bindClickListener(which); } } }); } private void setNegativeButton(AlertDialog.Builder builder) { final Bundle args = getArguments(); final CharSequence negativeButtonText = args.getCharSequence(NEGATIVE_BUTTON); if (negativeButtonText == null) { return; } final boolean useOnNegativeClickListener = args.getBoolean(NEGATIVE_CLICK_LISTENER); builder.setNegativeButton(negativeButtonText, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (useOnNegativeClickListener) { bindClickListener(which); } } }); } //============================================================ // ____ // / ___|___ _ __ ___ _ __ ___ ___ _ __ // | | / _ \| '_ ` _ \| '_ ` _ \ / _ \| '_ \ // | |__| (_) | | | | | | | | | | | (_) | | | | // \____\___/|_| |_| |_|_| |_| |_|\___/|_| |_| // // ____ _ _ ___ _ __ // | _ \(_) __ _| | ___ __ _|_ _|_ __ | |_ ___ _ __ / _| __ _ ___ ___ // | | | | |/ _` | |/ _ \ / _` || || '_ \| __/ _ \ '__| |_ / _` |/ __/ _ \ // | |_| | | (_| | | (_) | (_| || || | | | || __/ | | _| (_| | (_| __/ // |____/|_|\__,_|_|\___/ \__, |___|_| |_|\__\___|_| |_| \__,_|\___\___| // |___/ //============================================================ protected void bindClickListener(int which) { DialogFragmentCallback listener = getDialogFragmentCallback(); if (listener == null) { return; } switch (which) { case DialogInterface.BUTTON_POSITIVE: listener.onClickPositive(this); break; case DialogInterface.BUTTON_NEUTRAL: listener.onClickNeutral(this); break; case DialogInterface.BUTTON_NEGATIVE: listener.onClickNegative(this); break; default: break; } } protected void bindItemClickListener(int position) { DialogFragmentCallback listener = getDialogFragmentCallback(); if (listener != null) { listener.onItemClick(this, position); } } protected void bindSingleChoiceClickListener(int position) { DialogFragmentCallback listener = getDialogFragmentCallback(); if (listener != null) { listener.onSingleChoiceClick(this, position); } } protected void bindMultiChoiceClickListener(int position, boolean isChecked) { DialogFragmentCallback listener = getDialogFragmentCallback(); if (listener != null) { listener.onMultiChoiceClick(this, position, isChecked); } } }