写这篇文章,主要是记录一下如何打造自己的dialog。
今天这篇文章主要分为三个部分:
(1)dialog的使用方式
(2)dialog的源码阅读
(3)打造自己的dialog
一
使用dialog的步骤,通常为
(1)创建bulider
(2)设置参数
(3)创建AlerDialog
public void createNativeDialog(){
//创建Builder
AlertDialog.Builder alertDialogBuilder=new AlertDialog.Builder(this);
alertDialogBuilder.setTitle("安卓dialog");//设置标题
alertDialogBuilder.setIcon(R.mipmap.ic_launcher);//设置图表
alertDialogBuilder.setMessage("安卓dialog");//设置显示文本
alertDialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getApplicationContext(),"确定",Toast.LENGTH_SHORT).show();
}
});
alertDialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getApplicationContext(), "取消", Toast.LENGTH_SHORT).show();
}
});
/*设置下方按钮*/
//alertDialogBuilder.setPositiveButton();
// alertDialogBuilder.setNegativeButton();
// alertDialogBuilder.setNeutralButton();
/*对话框内容区域的设置提供了多种方法*/
//alertDialogBuilder.setItems();//设置对话框内容为简单列表项
//alertDialogBuilder.setSingleChoiceItems();//设置对话框内容为单选列表项
//alertDialogBuilder.setMultiChoiceItems();//设置对话框内容为多选列表项
//alertDialogBuilder.setAdapter();//设置对话框内容为自定义列表项
//alertDialogBuilder.setView();//设置对话框内容为自定义View
//设置对话框是否可取消 默认为true
alertDialogBuilder.setCancelable(false);
//setCancelListener(onCancelListener)
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
但是如果我们 2,3两个步骤颠倒一下,先创建AlertDialog,然后在builder里面设置参数,你会发现,生成的dialog什么都没有,只有一个背景。这是为什么呢?这就涉及到了dialog的源码了。先解决这个问题 ,然后去看一下源码。
其实这个很简单的,我们点击builder.create()方法()
public AlertDialog create() {
// We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
// so we always have to re-set the theme
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle(mCustomTitleView);
} else {
if (mTitle != null) {
dialog.setTitle(mTitle);
}
if (mIcon != null) {
dialog.setIcon(mIcon);
}
if (mIconId != 0) {
dialog.setIcon(mIconId);
}
if (mIconAttrId != 0) {
dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
}
}
if (mMessage != null) {
dialog.setMessage(mMessage);
}
if (mPositiveButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null);
}
if (mNegativeButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null);
}
if (mNeutralButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null);
}
// For a list, the client can either supply an array of items or an
// adapter or a cursor
if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
createListView(dialog);
}
if (mView != null) {
if (mViewSpacingSpecified) {
dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
mViewSpacingBottom);
} else {
dialog.setView(mView);
}
} else if (mViewLayoutResId != 0) {
dialog.setView(mViewLayoutResId);
}
/*
dialog.setCancelable(mCancelable);
dialog.setOnCancelListener(mOnCancelListener);
if (mOnKeyListener != null) {
dialog.setOnKeyListener(mOnKeyListener);
}
*/
}
这时候你会发现 apply()这个方法是真正设置参数的,那么你会问buulder里面不是也设置了参数吗?其实builder里面的参数都是存放在了AlertController.AlertParams 里面了(就是刚才调用apply()方法的P里面了)。我们在builder.setTitle的时候其实是把title的值存放在了P里面,我们看一下
public static class AlertParams {
public final Context mContext;
public final LayoutInflater mInflater;
public int mIconId = 0;
public Drawable mIcon;
public int mIconAttrId = 0;
public CharSequence mTitle;
public View mCustomTitleView;
public CharSequence mMessage;
public CharSequence mPositiveButtonText;
public DialogInterface.OnClickListener mPositiveButtonListener;
public CharSequence mNegativeButtonText;
public DialogInterface.OnClickListener mNegativeButtonListener;
public CharSequence mNeutralButtonText;
public DialogInterface.OnClickListener mNeutralButtonListener;
public boolean mCancelable;
public DialogInterface.OnCancelListener mOnCancelListener;
public DialogInterface.OnDismissListener mOnDismissListener;
public DialogInterface.OnKeyListener mOnKeyListener;
public CharSequence[] mItems;
public ListAdapter mAdapter;
public DialogInterface.OnClickListener mOnClickListener;
public int mViewLayoutResId;
public View mView;
public int mViewSpacingLeft;
public int mViewSpacingTop;
public int mViewSpacingRight;
public int mViewSpacingBottom;
public boolean mViewSpacingSpecified = false;
public boolean[] mCheckedItems;
public boolean mIsMultiChoice;
public boolean mIsSingleChoice;
public int mCheckedItem = -1;
public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
public Cursor mCursor;
public String mLabelColumn;
public String mIsCheckedColumn;
public boolean mForceInverseBackground;
public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
public OnPrepareListViewListener mOnPrepareListViewListener;
public boolean mRecycleOnMeasure = true;
public static class Builder {
private final AlertController.AlertParams P;//这个P
private final int mTheme;
public Builder setTitle(@StringRes int titleId) {
P.mTitle = P.mContext.getText(titleId);
return this;
}
/**
* Set the title displayed in the {@link Dialog}.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setTitle(@Nullable CharSequence title) {
P.mTitle = title;
return this;
}
当调用dialog.create()的时候,Params 会调用apply()方法,将具体的参数值赋值到dialog里面(其实是由AlertController.AlertParams传递到AlertController里面,dialog的所有真正的显示工作都是在controller中完成的)。
以上就是dialog的一般步骤,接下来 系统性的去阅读一下AlertDialog的源码吧。
-----------------------------------------------------------------------------------------------------------------------------------------
二.AleryDialog源码阅读
首先我们要弄清楚大体的 AlertDialog的轮廓。首先 要知道几个重要的类AlertDialog.Builder(暴露给开发者进行调用,设置参数,操作dialog,也是开发者能够一行代码生成Alertdiolog的原因),AlertController.AlertParams(这个类主要是存储dialog的参数),AlertController(这个类是真正操作dialog具体逻辑的类,几乎所有的核心代码都包含在了这个类里面)。
AlertDialog由三部分组成,自己的构造方法,一个内部Builder类,一个oncreate()方法。先看一下构造方法,
protected AlertDialog(@NonNull Context context) { this(context, 0); } /** * Construct an AlertDialog that uses an explicit theme. The actual style * 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 R.attr#alertDialogTheme}. */ protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) { super(context, resolveDialogTheme(context, themeResId)); mAlert = new AlertController(getContext(), this, getWindow()); } protected AlertDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) { this(context, 0); setCancelable(cancelable); setOnCancelListener(cancelListener); } static int resolveDialogTheme(@NonNull Context context, @StyleRes int resid) { if (resid >= 0x01000000) { // start of real resource IDs. return resid; } else { TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true); return outValue.resourceId; } }
构造方法中其实并没有什么难懂的地方,传上下文参数(一般都是弹窗所在的Activiy),Cance监听,样式(调用的方法是resolveDialogTheme(),这个会传到父类的dialog去处理,默认为0,如果为0则使用系统指定的样式)构造方法中最重要的就是调用了AlertController类。要想弄懂这个类,接下来看一下Buulder类
public static class Builder { private final AlertController.AlertParams P; private final int mTheme; /** * Creates a builder for an alert dialog that uses the default alert * dialog theme. * <p> * The default alert dialog theme is defined by * {@link android.R.attr#alertDialogTheme} within the parent * {@code context}'s theme. * * @param context the parent context */ public Builder(@NonNull Context context) { this(context, resolveDialogTheme(context, 0)); } /** * Creates a builder for an alert dialog that uses an explicit theme * resource. * <p> * The specified theme resource ({@code themeResId}) is applied on top * of the parent {@code context}'s theme. It may be specified as a * style resource containing a fully-populated theme, such as * {@link R.style#Theme_AppCompat_Dialog}, to replace all * attributes in the parent {@code context}'s theme including primary * and accent colors. * <p> * To preserve attributes such as primary and accent colors, the * {@code themeResId} may instead be specified as an overlay theme such * as {@link R.style#ThemeOverlay_AppCompat_Dialog}. This will * override only the window attributes necessary to style the alert * window as a dialog. * <p> * Alternatively, the {@code themeResId} may be specified as {@code 0} * to use the parent {@code context}'s resolved value for * {@link android.R.attr#alertDialogTheme}. * * @param context the parent context * @param themeResId the resource ID of the theme against which to inflate * this dialog, or {@code 0} to use the parent * {@code context}'s default alert dialog theme */ public Builder(@NonNull Context context, @StyleRes int themeResId) { P = new AlertController.AlertParams(new ContextThemeWrapper( context, resolveDialogTheme(context, themeResId))); mTheme = themeResId; } /** * Returns a {@link Context} with the appropriate theme for dialogs created by this Builder. * Applications should use this Context for obtaining LayoutInflaters for inflating views * that will be used in the resulting dialogs, as it will cause views to be inflated with * the correct theme. * * @return A Context for built Dialogs. */ @NonNull public Context getContext() { return P.mContext; } /** * Set the title using the given resource id. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setTitle(@StringRes int titleId) { P.mTitle = P.mContext.getText(titleId); return this; } /** * Set the title displayed in the {@link Dialog}. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setTitle(@Nullable CharSequence title) { P.mTitle = title; return this; } /** * Set the title using the custom view {@code customTitleView}. * <p> * The methods {@link #setTitle(int)} and {@link #setIcon(int)} should * be sufficient for most titles, but this is provided if the title * needs more customization. Using this will replace the title and icon * set via the other methods. * <p> * <strong>Note:</strong> To ensure consistent styling, the custom view * should be inflated or constructed using the alert dialog's themed * context obtained via {@link #getContext()}. * * @param customTitleView the custom view to use as the title * @return this Builder object to allow for chaining of calls to set * methods */ public Builder setCustomTitle(@Nullable View customTitleView) { P.mCustomTitleView = customTitleView; 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(@StringRes int messageId) { P.mMessage = P.mContext.getText(messageId); return this; } /** * Set the message to display. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setMessage(@Nullable CharSequence message) { P.mMessage = message; return this; } /** * Set the resource id of the {@link Drawable} to be used in the title. * <p> * Takes precedence over values set using {@link #setIcon(Drawable)}. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setIcon(@DrawableRes int iconId) { P.mIconId = iconId; return this; } /** * Set the {@link Drawable} to be used in the title. * <p> * <strong>Note:</strong> To ensure consistent styling, the drawable * should be inflated or constructed using the alert dialog's themed * context obtained via {@link #getContext()}. * * @return this Builder object to allow for chaining of calls to set * methods */ public Builder setIcon(@Nullable Drawable icon) { P.mIcon = icon; return this; } /** * Set an icon as supplied by a theme attribute. e.g. * {@link android.R.attr#alertDialogIcon}. * <p> * Takes precedence over values set using {@link #setIcon(int)} or * {@link #setIcon(Drawable)}. * * @param attrId ID of a theme attribute that points to a drawable resource. */ public Builder setIconAttribute(@AttrRes int attrId) { TypedValue out = new TypedValue(); P.mContext.getTheme().resolveAttribute(attrId, out, true); P.mIconId = out.resourceId; 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 * @param listener The {@link DialogInterface.OnClickListener} to use. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) { P.mPositiveButtonText = P.mContext.getText(textId); P.mPositiveButtonListener = listener; return this; } /** * 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 * @param listener The {@link DialogInterface.OnClickListener} to use. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setPositiveButton(CharSequence text, final OnClickListener listener) { P.mPositiveButtonText = text; P.mPositiveButtonListener = listener; 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 * @param listener The {@link DialogInterface.OnClickListener} to use. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNegativeButton(@StringRes int textId, final OnClickListener listener) { P.mNegativeButtonText = P.mContext.getText(textId); P.mNegativeButtonListener = listener; return this; } /** * 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 * @param listener The {@link DialogInterface.OnClickListener} to use. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNegativeButton(CharSequence text, final OnClickListener listener) { P.mNegativeButtonText = text; P.mNegativeButtonListener = listener; 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 * @param listener The {@link DialogInterface.OnClickListener} to use. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNeutralButton(@StringRes int textId, final OnClickListener listener) { P.mNeutralButtonText = P.mContext.getText(textId); P.mNeutralButtonListener = listener; return this; } /** * 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 * @param listener The {@link DialogInterface.OnClickListener} to use. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNeutralButton(CharSequence text, final OnClickListener listener) { P.mNeutralButtonText = text; P.mNeutralButtonListener = listener; return this; } /** * 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) { P.mCancelable = cancelable; 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(android.content.DialogInterface.OnDismissListener) * setOnDismissListener}.</p> * * @return This Builder object to allow for chaining of calls to set methods * @see #setCancelable(boolean) * @see #setOnDismissListener(android.content.DialogInterface.OnDismissListener) * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setOnCancelListener(OnCancelListener onCancelListener) { P.mOnCancelListener = onCancelListener; return this; } /** * Sets the callback that will be called when the dialog is dismissed for any reason. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setOnDismissListener(OnDismissListener onDismissListener) { P.mOnDismissListener = onDismissListener; return this; } /** * Sets the callback that will be called if a key is dispatched to the dialog. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setOnKeyListener(OnKeyListener onKeyListener) { P.mOnKeyListener = onKeyListener; 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(@ArrayRes int itemsId, final OnClickListener listener) { P.mItems = P.mContext.getResources().getTextArray(itemsId); P.mOnClickListener = listener; 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, final OnClickListener listener) { P.mItems = items; P.mOnClickListener = listener; return this; } /** * Set a list of items, which are supplied by the given {@link ListAdapter}, to be * displayed in the dialog as the content, you will be notified of the * selected item via the supplied listener. * * @param adapter The {@link ListAdapter} to supply the list of items * @param listener The listener that will be called when an item is clicked. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setAdapter(final ListAdapter adapter, final OnClickListener listener) { P.mAdapter = adapter; P.mOnClickListener = listener; return this; } /** * Set a list of items, which are supplied by the given {@link Cursor}, to be * displayed in the dialog as the content, you will be notified of the * selected item via the supplied listener. * * @param cursor The {@link Cursor} to supply the list of items * @param listener The listener that will be called when an item is clicked. * @param labelColumn The column name on the cursor containing the string to display * in the label. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setCursor(final Cursor cursor, final OnClickListener listener, String labelColumn) { P.mCursor = cursor; P.mLabelColumn = labelColumn; P.mOnClickListener = listener; 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. * @param listener notified when an item on the list is clicked. The dialog will not be * dismissed when an item is clicked. It will only be dismissed if clicked on a * button, if no buttons are supplied it's up to the user to dismiss the dialog. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems, final OnMultiChoiceClickListener listener) { P.mItems = P.mContext.getResources().getTextArray(itemsId); P.mOnCheckboxClickListener = listener; P.mCheckedItems = checkedItems; P.mIsMultiChoice = 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. * 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. * @param listener notified when an item on the list is clicked. The dialog will not be * dismissed when an item is clicked. It will only be dismissed if clicked on a * button, if no buttons are supplied it's up to the user to dismiss the dialog. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, final OnMultiChoiceClickListener listener) { P.mItems = items; P.mOnCheckboxClickListener = listener; P.mCheckedItems = checkedItems; P.mIsMultiChoice = 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. * 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 cursor the cursor used to provide the items. * @param isCheckedColumn specifies the column name on the cursor to use to determine * whether a checkbox is checked or not. It must return an integer value where 1 * means checked and 0 means unchecked. * @param labelColumn The column name on the cursor containing the string to display in the * label. * @param listener notified when an item on the list is clicked. The dialog will not be * dismissed when an item is clicked. It will only be dismissed if clicked on a * button, if no buttons are supplied it's up to the user to dismiss the dialog. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, final OnMultiChoiceClickListener listener) { P.mCursor = cursor; P.mOnCheckboxClickListener = listener; P.mIsCheckedColumn = isCheckedColumn; P.mLabelColumn = labelColumn; P.mIsMultiChoice = 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. 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. * @param listener notified when an item on the list is clicked. The dialog will not be * dismissed when an item is clicked. It will only be dismissed if clicked on a * button, if no buttons are supplied it's up to the user to dismiss the dialog. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem, final OnClickListener listener) { P.mItems = P.mContext.getResources().getTextArray(itemsId); P.mOnClickListener = listener; P.mCheckedItem = checkedItem; P.mIsSingleChoice = 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. 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 cursor the cursor to retrieve the items from. * @param checkedItem specifies which item is checked. If -1 no items are checked. * @param labelColumn The column name on the cursor containing the string to display in the * label. * @param listener notified when an item on the list is clicked. The dialog will not be * dismissed when an item is clicked. It will only be dismissed if clicked on a * button, if no buttons are supplied it's up to the user to dismiss the dialog. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn, final OnClickListener listener) { P.mCursor = cursor; P.mOnClickListener = listener; P.mCheckedItem = checkedItem; P.mLabelColumn = labelColumn; P.mIsSingleChoice = 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. 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. * @param listener notified when an item on the list is clicked. The dialog will not be * dismissed when an item is clicked. It will only be dismissed if clicked on a * button, if no buttons are supplied it's up to the user to dismiss the dialog. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) { P.mItems = items; P.mOnClickListener = listener; P.mCheckedItem = checkedItem; P.mIsSingleChoice = 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. 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 adapter The {@link ListAdapter} to supply the list of items * @param checkedItem specifies which item is checked. If -1 no items are checked. * @param listener notified when an item on the list is clicked. The dialog will not be * dismissed when an item is clicked. It will only be dismissed if clicked on a * button, if no buttons are supplied it's up to the user to dismiss the dialog. * * @return This Builder object to allow for chaining of calls to set methods */ public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final OnClickListener listener) { P.mAdapter = adapter; P.mOnClickListener = listener; P.mCheckedItem = checkedItem; P.mIsSingleChoice = true; return this; } /** * Sets a listener to be invoked when an item in the list is selected. * * @param listener the listener to be invoked * @return this Builder object to allow for chaining of calls to set methods * @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener) */ public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) { P.mOnItemSelectedListener = listener; return this; } /** * Set a custom view resource to be the contents of the Dialog. The * resource will be inflated, adding all top-level views to the screen. * * @param layoutResId Resource ID to be inflated. * @return this Builder object to allow for chaining of calls to set * methods */ public Builder setView(int layoutResId) { P.mView = null; P.mViewLayoutResId = layoutResId; P.mViewSpacingSpecified = false; return this; } /** * Sets a custom view to be the contents of the alert dialog. * <p> * When using a pre-Holo theme, if the supplied view is an instance of * a {@link ListView} then the light background will be used. * <p> * <strong>Note:</strong> To ensure consistent styling, the custom view * should be inflated or constructed using the alert dialog's themed * context obtained via {@link #getContext()}. * * @param view the view to use as the contents of the alert dialog * @return this Builder object to allow for chaining of calls to set * methods */ public Builder setView(View view) { P.mView = view; P.mViewLayoutResId = 0; P.mViewSpacingSpecified = false; return this; } /** * Set a custom view to be the contents of the Dialog, specifying the * spacing to appear around that view. If the supplied view is an * instance of a {@link ListView} the light background will be used. * * @param view The view to use as the contents of the Dialog. * @param viewSpacingLeft Spacing between the left edge of the view and * the dialog frame * @param viewSpacingTop Spacing between the top edge of the view and * the dialog frame * @param viewSpacingRight Spacing between the right edge of the view * and the dialog frame * @param viewSpacingBottom Spacing between the bottom edge of the view * and the dialog frame * @return This Builder object to allow for chaining of calls to set * methods * * * This is currently hidden because it seems like people should just * be able to put padding around the view. * @hide */ @RestrictTo(LIBRARY_GROUP) @Deprecated public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, int viewSpacingBottom) { P.mView = view; P.mViewLayoutResId = 0; P.mViewSpacingSpecified = true; P.mViewSpacingLeft = viewSpacingLeft; P.mViewSpacingTop = viewSpacingTop; P.mViewSpacingRight = viewSpacingRight; P.mViewSpacingBottom = viewSpacingBottom; 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 * @deprecated This flag is only used for pre-Material themes. Instead, * specify the window background using on the alert dialog * theme. */ @Deprecated public Builder setInverseBackgroundForced(boolean useInverseBackground) { P.mForceInverseBackground = useInverseBackground; return this; } /** * @hide */ @RestrictTo(LIBRARY_GROUP) public Builder setRecycleOnMeasureEnabled(boolean enabled) { P.mRecycleOnMeasure = enabled; return this; } /** * Creates an {@link AlertDialog} with the arguments supplied to this * builder. * <p> * Calling this method does not display the dialog. If no additional * processing is needed, {@link #show()} may be called instead to both * create and display the dialog. */ public AlertDialog create() { // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param, // so we always have to re-set the theme final AlertDialog dialog = new AlertDialog(P.mContext, mTheme); P.apply(dialog.mAlert); dialog.setCancelable(P.mCancelable); if (P.mCancelable) { dialog.setCanceledOnTouchOutside(true); } dialog.setOnCancelListener(P.mOnCancelListener); dialog.setOnDismissListener(P.mOnDismissListener); if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener); } return dialog; } /** * Creates an {@link AlertDialog} with the arguments supplied to this * builder and immediately displays the dialog. * <p> * Calling this method is functionally identical to: * <pre> * AlertDialog dialog = builder.create(); * dialog.show(); * </pre> */ public AlertDialog show() { final AlertDialog dialog = create(); dialog.show(); return dialog; } }
看到这个类我们应该明白了,我们所写的代码,调用的方法都包含在了这个类里面,这个类包含了一个成员变量AlertController.AlertParams ,它是AlertController的静态内部类,主要是存储一些dialog的参数;我们仔细看设置参数的set方法,所有的方法的返回值都是Builder,这就是我们能够构建链连续的设置参数,一行代码生成AlertDialog的原因了;Builder最后写了一个create()方法和一个show()方法,仔细看,show()方法中也调用了create(),所以一般我们直接调用show()就好了。
接下来我们分别来看一下create()和show();
先看create():
/** * Creates an {@link AlertDialog} with the arguments supplied to this * builder. * <p> * Calling this method does not display the dialog. If no additional * processing is needed, {@link #show()} may be called instead to both * create and display the dialog. */ public AlertDialog create() { // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param, // so we always have to re-set the theme final AlertDialog dialog = new AlertDialog(P.mContext, mTheme); P.apply(dialog.mAlert); dialog.setCancelable(P.mCancelable); if (P.mCancelable) { dialog.setCanceledOnTouchOutside(true); } dialog.setOnCancelListener(P.mOnCancelListener); dialog.setOnDismissListener(P.mOnDismissListener); if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener); } return dialog; }
上文已经讲过了,当调用dialog.create()的时候,Params 会调用apply()方法,将具体的参数值赋值到dialog里面(其实是由AlertController.AlertParams传递到AlertController里面,dialog的所有真正的显示工作都是在controller中完成的)。
接下来看show();
/** * Creates an {@link AlertDialog} with the arguments supplied to this * builder and immediately displays the dialog. * <p> * Calling this method is functionally identical to: * <pre> * AlertDialog dialog = builder.create(); * dialog.show(); * </pre> */ public AlertDialog show() { final AlertDialog dialog = create(); dialog.show(); return dialog; }
其实show方法真正调用的是其父类的dialog.show(),当我们点击dialog.show()的时候明显跳转到了Dialog.java的show()方法中。
讲了这么多,以上其实都是DIalog构建参数如何作用于Dialog,AlertController这个核心类我们还没有摸到边,接下来我们来进入AlertController中,首先从show()方法入手,看一下,dialog是如何被show()出来的。
/** * Start the dialog and display it on screen. The window is placed in the * application layer and opaque. Note that you should not override this * method to do initialization when the dialog is shown, instead implement * that in {@link #onStart}. */ public void show() { if (mShowing) { if (mDecor != null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } mDecor.setVisibility(View.VISIBLE); } return; } mCanceled = false; if (!mCreated) { dispatchOnCreate(null); } else { // Fill the DecorView in on any configuration changes that // may have occured while it was removed from the WindowManager. final Configuration config = mContext.getResources().getConfiguration(); mWindow.getDecorView().dispatchConfigurationChanged(config); } onStart(); mDecor = mWindow.getDecorView(); if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { final ApplicationInfo info = mContext.getApplicationInfo(); mWindow.setDefaultIcon(info.icon); mWindow.setDefaultLogo(info.logo); mActionBar = new WindowDecorActionBar(this); } WindowManager.LayoutParams l = mWindow.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); nl.copyFrom(l); nl.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; l = nl; } mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); }
这段代码的意思是:首先判断dialog是否显示出来过,如果是,重新设置顶级View---DecorView可见(DecorView是所有窗口布局的根view),接下来如果dialog没有被创建,则 dispatchOnCreate(null);这个方法很重要。如果创建了,则将Decview和WindowManager关联起来。dispatchOnCreate()这个方法确定了Dialog的布局界面。点击进入这个方法:
// internal method to make sure mCreated is set properly without requiring // users to call through to super in onCreate void dispatchOnCreate(Bundle savedInstanceState) { if (!mCreated) { onCreate(savedInstanceState); mCreated = true; } }
只是调用了onCreate();点击进入onCreate();
/** * Similar to {@link Activity#onCreate}, you should initialize your dialog * in this method, including calling {@link #setContentView}. * @param savedInstanceState If this dialog is being reinitialized after a * the hosting activity was previously shut down, holds the result from * the most recent call to {@link #onSaveInstanceState}, or null if this * is the first time. */ protected void onCreate(Bundle savedInstanceState) { }
发现里面是空的,这是Dialog.java下面的onCreate();也许是被重写了方法,所以我们进入一下AlertDialog.java下的onCreate()去看一下。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAlert.installContent(); }
发现调用了 mAlert.installContent(); mAlert 其实就是
public class AlertDialog extends AppCompatDialog implements DialogInterface { final AlertController mAlert;
这下总算是找到了AlertController这个类了。点击installContent()方法,去看一看:
public void installContent() { final int contentView = selectContentView(); mDialog.setContentView(contentView); setupView(); } private int selectContentView() { if (mButtonPanelSideLayout == 0) { return mAlertDialogLayout; } if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) { return mButtonPanelSideLayout; } return mAlertDialogLayout; }
这个方法完成了界面显示的工作,第一行代码调用selectContentView(),确定窗口的总体布局,这个布局在AlertController的 构造方法中
public AlertController(Context context, AppCompatDialog di, Window window) { mContext = context; mDialog = di; mWindow = window; mHandler = new ButtonHandler(di); //获得AlertDialog相关的属性集 final TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0); //获取不同布局在安卓系统中对应的id mAlertDialogLayout = a.getResourceId(R.styleable.AlertDialog_android_layout, 0);//这里呢!总体的布局 mButtonPanelSideLayout = a.getResourceId(R.styleable.AlertDialog_buttonPanelSideLayout, 0); mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0); mMultiChoiceItemLayout = a.getResourceId(R.styleable.AlertDialog_multiChoiceItemLayout, 0); mSingleChoiceItemLayout = a .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0); mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0); mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true); a.recycle(); /* We use a custom title so never request a window title */ di.supportRequestWindowFeature(Window.FEATURE_NO_TITLE); }
构造方法主要是玩成了初始化工作。从Dialog中获取了Context,Window对象,并创建了一个ButtonHandler作为点击按钮的一个消息处理类,来处理dialog按钮的点击事件,接下来就是对AlertDialog需要用到的各种布局的初始化。
private static final class ButtonHandler extends Handler { // Button clicks have Message.what as the BUTTON{1,2,3} constant private static final int MSG_DISMISS_DIALOG = 1; private WeakReference<DialogInterface> mDialog; public ButtonHandler(DialogInterface dialog) { mDialog = new WeakReference<>(dialog); } @Override public void handleMessage(Message msg) { switch (msg.what) { case DialogInterface.BUTTON_POSITIVE: case DialogInterface.BUTTON_NEGATIVE: case DialogInterface.BUTTON_NEUTRAL: ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what); break; case MSG_DISMISS_DIALOG: ((DialogInterface) msg.obj).dismiss(); } } }
这个是mAlertDialogLayout的总体布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/parentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingTop="9dip" android:paddingBottom="3dip" android:paddingStart="3dip" android:paddingEnd="1dip"> <LinearLayout android:id="@+id/topPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="54dip" android:orientation="vertical"> <LinearLayout android:id="@+id/title_template" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:layout_marginTop="6dip" android:layout_marginBottom="9dip" android:layout_marginStart="10dip" android:layout_marginEnd="10dip"> <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top" android:paddingTop="6dip" android:paddingEnd="10dip" android:src="@mipmap/ic_launcher" android:paddingRight="10dip" /> <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle" style="?android:attr/textAppearanceLarge" android:singleLine="true" android:ellipsize="end" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAlignment="viewStart" /> </LinearLayout> <ImageView android:id="@+id/titleDivider" android:layout_width="match_parent" android:layout_height="1dip" android:visibility="gone" android:scaleType="fitXY" android:gravity="fill_horizontal" android:src="@android:drawable/divider_horizontal_dark" /> <!-- If the client uses a customTitle, it will be added here. --> </LinearLayout> <LinearLayout android:id="@+id/contentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <ScrollView android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="2dip" android:paddingBottom="12dip" android:paddingStart="14dip" android:paddingEnd="10dip" android:overScrollMode="ifContentScrolls" android:paddingLeft="14dip" android:paddingRight="10dip"> <TextView android:id="@+id/message" style="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="5dip" /> </ScrollView> </LinearLayout> <FrameLayout android:id="@+id/customPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1"> <FrameLayout android:id="@+android:id/custom" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="5dip" android:paddingBottom="5dip" /> </FrameLayout> <LinearLayout android:id="@+id/buttonPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="54dip" android:orientation="vertical" > <LinearLayout style="?android:attr/buttonBarStyle" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="4dip" android:paddingStart="2dip" android:paddingEnd="2dip" android:measureWithLargestChild="true"> <LinearLayout android:id="@+id/leftSpacer" android:layout_weight="0.25" android:layout_width="0dip" android:layout_height="wrap_content" android:orientation="horizontal" android:visibility="gone" /> <Button android:id="@+id/button1" android:layout_width="0dip" android:layout_gravity="start" android:layout_weight="1" style="?android:attr/buttonBarButtonStyle" android:maxLines="2" android:layout_height="wrap_content" /> <Button android:id="@+id/button3" android:layout_width="0dip" android:layout_gravity="center_horizontal" android:layout_weight="1" style="?android:attr/buttonBarButtonStyle" android:maxLines="2" android:layout_height="wrap_content" /> <Button android:id="@+id/button2" android:layout_width="0dip" android:layout_gravity="end" android:layout_weight="1" style="?android:attr/buttonBarButtonStyle" android:maxLines="2" android:layout_height="wrap_content" /> <LinearLayout android:id="@+id/rightSpacer" android:layout_width="0dip" android:layout_weight="0.25" android:layout_height="wrap_content" android:orientation="horizontal" android:visibility="gone" /> </LinearLayout> </LinearLayout> </LinearLayout>
简化之后是这个样子
回到installContent,接下来就执行mDialog.setContentView(contentView);将这个布局填充到dialog上,去执行setUpView()。其实这个方法看到这里就可以了如果你往更深入的地方看的话,你会发现一个类AppCompatDelegate类
@Override public void setContentView(@LayoutRes int layoutResID) { getDelegate().setContentView(layoutResID);//得到具体版本代理对象,将这个布局填充到dialog上 }得到版本代理
/** * @return The {@link AppCompatDelegate} being used by this Dialog. */ public AppCompatDelegate getDelegate() { if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; }创建版本代理
/** * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code dialog}. * * @param callback An optional callback for AppCompat specific events */ public static AppCompatDelegate create(Dialog dialog, AppCompatCallback callback) { return create(dialog.getContext(), dialog.getWindow(), callback);//创建版本代理
}
主要通过AppCompatDelegate类,创建不同的版本代理,
private static AppCompatDelegate create(Context context, Window window, AppCompatCallback callback) { final int sdk = Build.VERSION.SDK_INT; if (BuildCompat.isAtLeastN()) { return new AppCompatDelegateImplN(context, window, callback); } else if (sdk >= 23) { return new AppCompatDelegateImplV23(context, window, callback); } else if (sdk >= 14) { return new AppCompatDelegateImplV14(context, window, callback); } else if (sdk >= 11) { return new AppCompatDelegateImplV11(context, window, callback); } else { return new AppCompatDelegateImplV9(context, window, callback); } }
好了 接下来我们继续 执行setupView();
private void setupView() { final View parentPanel = mWindow.findViewById(R.id.parentPanel); final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel); final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel); final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel); // Install custom content before setting up the title or buttons so // that we can handle panel overrides. final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel); setupCustomContent(customPanel); final View customTopPanel = customPanel.findViewById(R.id.topPanel); final View customContentPanel = customPanel.findViewById(R.id.contentPanel); final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel); // Resolve the correct panels and remove the defaults, if needed. final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel); final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel); final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel); setupContent(contentPanel); setupButtons(buttonPanel); setupTitle(topPanel); final boolean hasCustomPanel = customPanel != null && customPanel.getVisibility() != View.GONE; final boolean hasTopPanel = topPanel != null && topPanel.getVisibility() != View.GONE; final boolean hasButtonPanel = buttonPanel != null && buttonPanel.getVisibility() != View.GONE; // Only display the text spacer if we don't have buttons. if (!hasButtonPanel) { if (contentPanel != null) { final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons); if (spacer != null) { spacer.setVisibility(View.VISIBLE); } } } if (hasTopPanel) { // Only clip scrolling content to padding if we have a title. if (mScrollView != null) { mScrollView.setClipToPadding(true); } // Only show the divider if we have a title. View divider = null; if (mMessage != null || mListView != null || hasCustomPanel) { if (!hasCustomPanel) { divider = topPanel.findViewById(R.id.titleDividerNoCustom); } } if (divider != null) { divider.setVisibility(View.VISIBLE); } } else { if (contentPanel != null) { final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle); if (spacer != null) { spacer.setVisibility(View.VISIBLE); } } } if (mListView instanceof RecycleListView) { ((RecycleListView) mListView).setHasDecor(hasTopPanel, hasButtonPanel); } // Update scroll indicators as needed. if (!hasCustomPanel) { final View content = mListView != null ? mListView : mScrollView; if (content != null) { final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0) | (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0); setScrollIndicators(contentPanel, content, indicators, ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM); } } final ListView listView = mListView; if (listView != null && mAdapter != null) { listView.setAdapter(mAdapter); final int checkedItem = mCheckedItem; if (checkedItem > -1) { listView.setItemChecked(checkedItem, true); listView.setSelection(checkedItem); } } }
该方法具体指定了Dialog的界面布局,虽然很长但是。
这样子 一个dialog就被show了出来。感觉是不是很蒙。总结一下:
(1)创建AlertDialog的内部类Builder ,将参数保存到AlertController内部类的AlertParams中;
(2)AlertDialog调用Builder.create()方法new一个AlertDialog,并将之前在AlertParams中保存的数据取出来传入AlertController里面;
(3)AlertDialog调用show()(Dialog.show()),会调用AlertDialog的AlertController的installContent()方法,根据之前传递的参数设置弹窗的界面;
(4)show()最后将弹窗显示出来。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
三.打造自己的dialog
先说下思路
(1)首先定义一个AlertDialog,继承Dialog。重写AlertDialog的构造方法,创建内部类Builder.
(2)创建一个AlertController类,这个类是用来具体操作Dialog的,这个类里面创建一个内部类AlertParams,用来存放数据参数。
(3)创建一个DialogViewHelper类,用来辅助处理View。
AlertDialog 代码:
public class AlertDialog extends Dialog{ private AlertController mAlert; public AlertDialog(Context context, int themeResId) { super(context, themeResId); mAlert=new AlertController(this,getWindow()); } public AlertDialog(Context context) { super(context); } //设置文本 public void setText(int viewId, CharSequence text) { mAlert.setText(viewId,text); } public <T extends View> T getView(int viewId) { return mAlert.getView(viewId); } //设置点击事件 public void setOnClickListener(int viewId, View.OnClickListener listener) { mAlert.setOnClickListener(viewId, listener); } public static class Builder{ private AlertController.AlertParams P; /** * Creates a builder for an alert dialog that uses the default alert * dialog theme. */ public Builder(Context context) { this(context, R.style.dialog); } /** * Creates a builder for an alert dialog that uses an explicit theme * resource. * <p> * The specified theme resource ({@code themeResId}) is applied on top * of the parent {@code context}'s theme. It may be specified as a * style resource containing a fully-populated theme, such as * {@link android.support.v7.appcompat.R.style#Theme_AppCompat_Dialog}, to replace all * attributes in the parent {@code context}'s theme including primary * and accent colors. * <p> * To preserve attributes such as primary and accent colors, the * {@code themeResId} may instead be specified as an overlay theme such * as {@link android.support.v7.appcompat.R.style#ThemeOverlay_AppCompat_Dialog}. This will * override only the window attributes necessary to style the alert * window as a dialog. * <p> * Alternatively, the {@code themeResId} may be specified as {@code 0} * to use the parent {@code context}'s resolved value for * {@link android.R.attr#alertDialogTheme}. * * @param context the parent context * @param themeResId the resource ID of the theme against which to inflate * this dialog, or {@code 0} to use the parent * {@code context}'s default alert dialog theme */ public Builder(@NonNull Context context, @StyleRes int themeResId) { P = new AlertController.AlertParams(context,themeResId); } /** * 设置布局内容的layout的ID * */ public Builder setContentView(View view) { P.mView = view; P.mViewLayoutResId = 0; return this; } public Builder setContentView(int layoutResId) { P.mView = null; P.mViewLayoutResId = layoutResId; return this; } //设置文本 public Builder setText(int viewId,CharSequence text){ P.mTextArray.put(viewId,text); return this; } //设置点击事件 public Builder onClickListener(int viewId,View.OnClickListener listenner){ P.mClickArray.put(viewId, listenner); return this; } /** * 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 AlertDialog.Builder setCancelable(boolean cancelable) { P.mCancelable = cancelable; 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(android.content.DialogInterface.OnDismissListener) * setOnDismissListener}.</p> * * @return This Builder object to allow for chaining of calls to set methods * @see #setCancelable(boolean) * @see #setOnDismissListener(android.content.DialogInterface.OnDismissListener) * * @return This Builder object to allow for chaining of calls to set methods */ public AlertDialog.Builder setOnCancelListener(OnCancelListener onCancelListener) { P.mOnCancelListener = onCancelListener; return this; } /** * Sets the callback that will be called when the dialog is dismissed for any reason. * * @return This Builder object to allow for chaining of calls to set methods */ public AlertDialog.Builder setOnDismissListener(OnDismissListener onDismissListener) { P.mOnDismissListener = onDismissListener; return this; } /** * Sets the callback that will be called if a key is dispatched to the dialog. * * @return This Builder object to allow for chaining of calls to set methods */ public AlertDialog.Builder setOnKeyListener(OnKeyListener onKeyListener) { P.mOnKeyListener = onKeyListener; return this; } //设置一些万能的参数 //全屏加载 public Builder setFullWidth(){ P.mWidth= ViewGroup.LayoutParams.MATCH_PARENT; return this; } //从底部上移动画 public Builder setFromBottom(boolean isAnimations){ if (isAnimations){ P.mAnimations=R.style.dialog_from_bottom_anim; } P.mGravity= Gravity.BOTTOM; P.mWidth= ViewGroup.LayoutParams.MATCH_PARENT; return this; } // public Builder setWidthAndHeight(int width,int height){ P.mWidth=width; P.mHeight=height; return this; } //添加默认动画 public Builder addDefaultAnimation(){ P.mAnimations=R.style.dialog_scale_anim; return this; } //添加其他动画 public Builder setAnimation(int styleAnimation){ P.mAnimations=styleAnimation; return this; } /** * Creates an {@link android.support.v7.app.AlertDialog} with the arguments supplied to this * builder. * <p> * Calling this method does not display the dialog. If no additional * processing is needed, {@link #show()} may be called instead to both * create and display the dialog. */ public AlertDialog create() { // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param, // so we always have to re-set the theme final AlertDialog dialog = new AlertDialog(P.mContext, P.mThemeResId); P.apply(dialog.mAlert); dialog.setCancelable(P.mCancelable); if (P.mCancelable) { dialog.setCanceledOnTouchOutside(true); } dialog.setOnCancelListener(P.mOnCancelListener); dialog.setOnDismissListener(P.mOnDismissListener); if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener); } return dialog; } /** * Creates an {@link android.support.v7.app.AlertDialog} with the arguments supplied to this * builder and immediately displays the dialog. * <p> * Calling this method is functionally identical to: * <pre> * AlertDialog dialog = builder.create(); * dialog.show(); * </pre> */ public AlertDialog show() { final AlertDialog dialog = create(); dialog.show(); return dialog; } }
AlertController 代码:
class AlertController { private AlertDialog mDialog; private Window mWindow; private DialogViewHelper mViewHelper; public AlertController(AlertDialog Dialog, Window window) { this.mDialog=Dialog; this.mWindow=window; } public AlertDialog getDialog() { return mDialog; } public Window getWindow() { return mWindow; } //设置文本 public void setText(int viewId, CharSequence text) { mViewHelper.setText(viewId,text); } public <T extends View> T getView(int viewId) { return mViewHelper.getView(viewId); } //设置点击事件 public void setOnClickListener(int viewId,View.OnClickListener listener) { mViewHelper.setOnClickListener(viewId,listener); } public void setViewHelper(DialogViewHelper mViewHelper) { this.mViewHelper = mViewHelper; } public static class AlertParams{ public Context mContext; public int mThemeResId; //点击空白是否可以取消 public boolean mCancelable=true; //dialog 取消监听 public DialogInterface.OnCancelListener mOnCancelListener; //dialog消失监听 public DialogInterface.OnDismissListener mOnDismissListener; //dialog按键监听 public DialogInterface.OnKeyListener mOnKeyListener; //dialog显示的布局 public View mView; //dialog显示的布局的ID public int mViewLayoutResId; //存放字体的修改 public SparseArray<CharSequence>mTextArray=new SparseArray<>(); //存放点击事件 public SparseArray<View.OnClickListener>mClickArray=new SparseArray<>(); //宽度 public int mWidth= ViewGroup.LayoutParams.WRAP_CONTENT; //高度 public int mHeight=ViewGroup.LayoutParams.WRAP_CONTENT; //动画 public int mAnimations=0; //位置 public int mGravity= Gravity.CENTER; public AlertParams(Context context, int themeResId) { this.mContext=context; this.mThemeResId=themeResId; } public void apply(AlertController mAlert) { //1.设置dialog布局 DialogViewHelper viewHelper=null; if (mViewLayoutResId!=0){ viewHelper=new DialogViewHelper(mContext,mViewLayoutResId); } if (mView!=null){ viewHelper=new DialogViewHelper(); viewHelper.setContentView(mView); } if (viewHelper==null){ throw new IllegalArgumentException("请设置布局,调用setContView()"); } //给dialog设置布局\ mAlert.getDialog().setContentView(viewHelper.getContentView()); //设置AlertController的辅助类 mAlert.setViewHelper(viewHelper); //2设置文本 int textArraySize=mTextArray.size(); for (int i=0;i<textArraySize;i++ ){ mAlert.setText(mTextArray.keyAt(i),mTextArray.valueAt(i)); } //3设置点击事件 int clickArraySize=mClickArray.size(); for (int i=0;i<clickArraySize;i++ ){ mAlert.setOnClickListener(mClickArray.keyAt(i), mClickArray.valueAt(i)); } Window window=mAlert.getWindow(); window.setGravity(mGravity); //4.设置动画 if (mAnimations!=0){ window.setWindowAnimations(mAnimations); } //设置宽高 WindowManager.LayoutParams params= window.getAttributes(); params.width=mWidth; params.height=mHeight; window.setAttributes(params); } } }
DialogViewHelper代码:
class DialogViewHelper { private View mContentView = null; //防止霸气侧漏 private SparseArray<WeakReference<View>> mViews; public DialogViewHelper(Context mContext, int mViewLayoutResId) { this(); mContentView = LayoutInflater.from(mContext).inflate(mViewLayoutResId, null); } public DialogViewHelper() { mViews = new SparseArray<>(); } //设置布局 public void setContentView(View mView) { this.mContentView = mView; } //设置文本 public void setText(int viewId, CharSequence text) { TextView textView = getView(viewId); if (textView != null) { textView.setText(text); } } public <T extends View> T getView(int viewId) { //侧漏的问题 WeakReference<View> viewReference = mViews.get(viewId); // View view=mViews.get(viewId).get(); View view = null; if (viewReference != null) { view = viewReference.get(); } if (view == null) { view = mContentView.findViewById(viewId); if (view != null) { mViews.put(viewId, new WeakReference<View>(view)); } } return (T) view; } //设置点击事件 public void setOnClickListener(int viewId, View.OnClickListener listener) { View view = getView(viewId); if (view != null) { view.setOnClickListener( listener); } } //获取Content内容的View public View getContentView() { return mContentView; } }
最后的 最后,扩展性的东西可以根据自己的需求去做。
最后上传 GItHub:https://github.com/yuyunhai/DialogUtils