打造自己的dialog

来源:互联网 发布:visio mac版 编辑:程序博客网 时间:2024/05/18 17:44

写这篇文章,主要是记录一下如何打造自己的dialog。



今天这篇文章主要分为三个部分:

(1)dialog的使用方式

(2)dialog的源码阅读

(3)打造自己的dialog

使用dialog的步骤,通常为

(1)创建bulider

(2)设置参数

(3)创建AlerDialog

(4)显示AlerDialog,其实调用show()方法就行了 第三部可以省略,show方法中也调用了create(),看一下源码即可

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;}

创建一个dialog,AlertParams调用apply()方法,判定触摸取消,然后是取消监听,和消失监听,键盘事件监听
这里面的内容大多数都可以看懂,我们点击P.apply(dialog.mAlert)中(P代表的是AlertParams,所有的属性参数都在里面 ),

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 onCreatevoid 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()去看一下。

@Overrideprotected 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类

@Overridepublic 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


0 1
原创粉丝点击