在对AlertDialog进行封装之前,我们有必要先了解一下AlertDialog的源码实现。一般情况下一行代码我们就可以创建一个弹框,如下所示:
new AlertDialog.Builder(this).create().show();
下面我们就一段段来分析,首先点击进去看下AlertDialog
public class AlertDialog extends AppCompatDialog implements DialogInterface {
...
}
public class AppCompatDialog extends Dialog implements AppCompatCallback {
...
}
也就是说我们的AlertDialog是继承自AppCompatDialog,而AppCompatDialog是继承自我们的Dialog,这个关系知道是怎么一回事就可以了,接下来进入到Builder里面。
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;
}
...
}
可以看到,我们的Builder并不是直接在AlertDialog里面去创建方法,而是创建了一个静态内部类Builder,为什么会这样设计,了解设计模式的朋友可能会明白,这是用了建造者设计模式,便于为最后生成的AlertDialog进行个性化定制。在Builder里面有两个构造方法:
public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(@NonNull Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
mTheme = themeResId;
}
注意看,第一个构造方法最终会执行到第二个构造方法里面,那么两个构造方法的区别是什么呢?注意看第二个构造方法的第二个参数themeResId,意思很明显AlertDialog的设置的主题,所以这里我们就很好去理解了,即:
在创建AlertDialog的Builder的时候,如果我们不给AlertDialog指定主题,则系统会默认使用系统自带的主题样式。
继续研究构造函数,里面有一个很重要的参数:P。这个P是在Builder里面声明的一个静态类:
public static class Builder {
private final AlertController.AlertParams P;
private final int mTheme;
...
}
在调用构造函数的时候会实例化P。那么这个P又是什么呢?点进去看看:
class AlertController {
public static class AlertParams {
public final Context mContext;
public final LayoutInflater mInflater;
}
}
可以很明显的看到,P是AlertController类里面的一个静态内部类,我们先知道这个概念,后面再详细介绍。
接下来我们进入到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;
}
我们一行一行来看:
首先创建了一个AlertDialog的实例
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
mAlert = new AlertController(getContext(), this, getWindow());
}
在这里创建了AlertController这个类,在AlertController这个构造方法里面主要是去设置一些属性
public AlertController(Context context, AppCompatDialog di, Window window) {
mContext = context;
mDialog = di;
mWindow = window;
mHandler = new ButtonHandler(di);
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog,
R.attr.alertDialogStyle, 0);
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);
mButtonIconDimen = a.getDimensionPixelSize(R.styleable.AlertDialog_buttonIconDimen, 0);
a.recycle();
/* We use a custom title so never request a window title */
di.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);