Android源码中,最常用到的Builder模式就是AlertDialog.Builder,使用Builder来构建复杂的AlertDialog对象。在开发中,我们经常用到AlertDialog,具体示例如下:
/** * 显示基本的AlertDialog * @param context */
private void showDialog(Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setIcon(R.mipmap.ic_launcher);
builder.setTitle("Title");
builder.setMessage("Message");
builder.setPositiveButton("Button1", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setTitle("点击了对话框上的Button1");
}
});
builder.setNegativeButton("Button2", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setTitle("点击了对话框上的Button2");
}
});
builder.setNeutralButton("Button3", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setTitle("点击了对话框上的Button3");
}
});
builder.create().show();
}
显示结果如下图:
从类名就可以看出这就是一个Builder模式,通过Builder对象来组装Dialog的各个部分,如title、buttons、Message等,将Dialog的构造和表示进行分离。下面看看AlertDialog的相关源码:
public class AlertDialog extends Dialog implements DialogInterface {
// AlertController接收Builder成员变量P中的各个参数
private AlertController mAlert;
// 构造AlertDialog
protected AlertDialog(Context context) {
this(context, 0);
}
// 构造AlertDialog
AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
// 构造AlertController
mAlert = new AlertController(getContext(), this, getWindow());
}
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
// 实际上调用的是mAlert的setTitle方法
mAlert.setTitle(title);
}
/** * @see Builder#setCustomTitle(View) */
public void setCustomTitle(View customTitleView) {
// 实际上调用的是mAlert的setCustomTitle方法
mAlert.setCustomTitle(customTitleView);
}
public void setMessage(CharSequence message) {
// 实际上调用的是mAlert的setMessage方法
mAlert.setMessage(message);
}
// 省略代码
// **************Builder为AlertDialog的内部类****************
public static class Builder {
// 1. 存储AlertDialog的各个参数,如title、message、icon等
private final AlertController.AlertParams P;
// 代码省略
public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(Context context, int theme) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, theme)));
mTheme = theme;
}
// 省略代码
// 2. 设置各种参数
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
public Builder setCustomTitle(View customTitleView) {
P.mCustomTitleView = customTitleView;
return this;
}
// 3. 构建AlertDialog,传递参数
public AlertDialog create() {
// 4. 调用new AlertDialog构造对象,并且将参数传递给AlertDialog
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
// 5. 将P中的参数应用到dialog中的mAlert对象中
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;
}
上述代码中,Builder类可以设置AlertDialog中的title、message、button等参数,这些参数都存储在类型为AlertController.AlertParams的成员变量中P中,AlertController.AlertParams中包含了与AlertDialog视图中对应的成员变量。在调用Builder类的create函数时会创建AlertDialog,并且Builder成员变量P中保存的参数应用到AlertDialog的mAlert对象中,即P.apply(dialog.mAlert)代码。我们在看看apply函数的实现:
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);
}
if (mForceInverseBackground) {
dialog.setInverseBackgroundForced(true);
}
// For a list, the client can either supply an array of items or an
// adapter or a cursor
// 如果设置了mItems,则表示是单选或者多选列表,此时创建一个ListView
if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
createListView(dialog);
}
// 将mView设置给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函数中,只是将AlertParams参数设置到AlertController中,例如,将标题设置到Dialog对应的标题视图中,将Message设置到内容视图中等。当我们获取到AlertDialog对象后,通过show函数就可以显示这个对话框。我们看看Dialog的show函数(该函数在Dialog类中):
public void show() {
// 已经是显示状态则return
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;
// 1. onCreate调用
if (!mCreated) {
dispatchOnCreate(null);
}
// 2. onStart
onStart();
// 3. 获取DecorView
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);
}
// 4. 获取布局参数
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;
}
try {
// 5. 将mDecor添加到WindowManager中
mWindowManager.addView(mDecor, l);
mShowing = true;
// 发送一个显示Dialog的消息
sendShowMessage();
} finally {
}
}
在show函数中主要做了如下几个事情:
1. 通过dispatchOnCreate函数来调用AlertDialog的onCreate函数;
2. 然后调用AlertDialog的onStart函数;
3. 最后将Dialog的DecorView添加到WindowManager中。
很明显,这就是一系列典型的声明周期函数。那么按照惯例,AlertDialog的内容视图构建按理应该在onCreate函数中,我们来看看是不是:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 调用了AlertController的installContent方法
mAlert.installContent();
}
在onCreate函数中主要调用了AlertController的installContent方法,Dialog中的onCreate函数只是一个空实现而已,可以忽略它。那么AlertDialog的视图内容必然救灾installContent函数中,继续深入了解吧:
public void installContent() {
/* We use a custom title so never request a window title */
// 设置窗口,没有title
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
int contentView = selectContentView();
// 设置窗口的内容视图布局
mWindow.setContentView(contentView);
// 初始化AlertDialog其他视图内容
setupView();
setupDecor();
}
installContent函数的代码很少,但极为重要,它调用了Window对象的setContentView,这个setContentView就与Activity中的一模一样,实际上Activity最终也是调用Window对象的setContentView函数。因此,这里就是设置AlertDialog的内容布局,这个布局就是mAlertDialogLayout字段的值,这个值在AlertController的构造函数中进行了初始化,具体代码如下:
public AlertController(Context context, DialogInterface di, Window window) {
mContext = context;
mDialogInterface = di;
mWindow = window;
mHandler = new ButtonHandler(di);
TypedArray a = context.obtainStyledAttributes(null,
com.android.internal.R.styleable.AlertDialog,
com.android.internal.R.attr.alertDialogStyle, 0);
// AlertDialog的布局id,就是alert_dialog.xml
mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout,
com.android.internal.R.layout.alert_dialog);
mButtonPanelSideLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_buttonPanelSideLayout, 0);
mListLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_listLayout,
com.android.internal.R.layout.select_dialog);
mMultiChoiceItemLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_multiChoiceItemLayout,
com.android.internal.R.layout.select_dialog_multichoice);
mSingleChoiceItemLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout,
com.android.internal.R.layout.select_dialog_singlechoice);
mListItemLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_listItemLayout,
com.android.internal.R.layout.select_dialog_item);
a.recycle();
}
从AlertController的构造函数中可以看到,AlertDialog的布局资源就是alert_dialog.xml这个文件,其效果图如下:
当通过Builder对象setTitle、setMessage等方法设置具体内容时,就是将这些内容填充到对应的视图中。而AlertDialog也允许你通过setView传入内容视图,这个内容视图就是替换掉上图中的Message区域,AlertDialog预留了一个costomPanel区域用来显示用户自定义内容视图。我们来看看setupView函数:
private void setupView() {
// 1. 获取并初始化内容区域
final LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel);
setupContent(contentPanel);
// 2. 初始化按钮
final boolean hasButtons = setupButtons();
// 3. 获取并初始化title区域
final LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel);
final TypedArray a = mContext.obtainStyledAttributes(
null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
final boolean hasTitle = setupTitle(topPanel);
// 按钮区域的可见性
final View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
if (!hasButtons) {
buttonPanel.setVisibility(View.GONE);
final View spacer = mWindow.findViewById(R.id.textSpacerNoButtons);
if (spacer != null) {
spacer.setVisibility(View.VISIBLE);
}
mWindow.setCloseOnTouchOutsideIfNotSet(true);
}
// 4. 自定义内容视图区域
final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
final View customView;
if (mView != null) {
customView = mView;
} else if (mViewLayoutResId != 0) {
final LayoutInflater inflater = LayoutInflater.from(mContext);
customView = inflater.inflate(mViewLayoutResId, customPanel, false);
} else {
customView = null;
}
final boolean hasCustomView = customView != null;
if (!hasCustomView || !canTextInput(customView)) {
mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
}
if (hasCustomView) {
final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
if (mViewSpacingSpecified) {
custom.setPadding(
mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
}
if (mListView != null) {
((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
}
} else {
customPanel.setVisibility(View.GONE);
}
// Only display the divider if we have a title and a custom view or a
// message.
if (hasTitle) {
final View divider;
if (mMessage != null || customView != null || mListView != null) {
divider = mWindow.findViewById(R.id.titleDivider);
} else {
divider = mWindow.findViewById(R.id.titleDividerTop);
}
if (divider != null) {
divider.setVisibility(View.VISIBLE);
}
}
// 设置背景
setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, hasTitle, hasCustomView,
hasButtons);
a.recycle();
}
这个setupView顾名思义就是初始化AlertDialog布局中的各个部分,如标题区域、按钮区域、内容区域等,在该函数调用之后整个Dialog的视图内容全部设置完毕。而各个区域的视图都属于mAlertDialogLayout布局中的子元素,Window对象有关联了mAlertDialogLayout的整个布局树,当调用完setupView之后整个视图树的数据都填充完毕,当用户调用show函数时,WindowManager将会Window对象的DecorView(也就是mAlertDialogLayout对应的视图,当然DecorView还有一个层次,我们不做过多讨论)添加到用户的窗口上,并显示出来。至此,整个Dialog就出现在用户的视野中了!
在AlertDialog的Builder模式中并没有看到Director角色的出现,其实在很多场景中,Android并没有完全按照GOF在《设计模式:可服用面向对象软件的基础》一书中描述的经典模式实现来做,而是做了一些修改,使得这些模式更容易使用。这里的AlertDialog.Builder同时扮演了builder、ConcreteBuilder、Director的角色,简化了Builder模式的设计。关于Builder模式的讲解可以参考《复杂对象的组装与创建——建造者模式》一文。当模块比较稳定不存在一些变化时,可以在经典模式实现的基础上做出一些精简,而不是照搬GOF上的经典实现,更不要生搬硬套,是程序失去架构之美。正式由于灵活地运用设计模式,Android的源码很值得我们去学习。