打造自己的dialog

写这篇文章,主要是记录一下如何打造自己的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 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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值