前言:
在Android源码中最常用到的Builder模式,就是AlertDialog.Builder,使用该Builder来
构建复杂的AlertDialog对象。
首先来看使用方法:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Title").setMessage("Message").setIcon(R.mipmap.ic_launcher).create().show();
从这个使用方式来看明显就是一个Builder模式,对外隐藏了内部的实现细节,将Dialog的各个组成部分在
Builder中进行组装。下面看AlertDialog的相关源码:
public class AlertDialog extends AppCompatDialog implements DialogInterface {
//接收Builder成员变量P中的参数
private AlertController mAlert;
protected AlertDialog(Context context) {
this(context, resolveDialogTheme(context, 0), true);
}
protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
super(context, resolveDialogTheme(context, 0));
setCancelable(cancelable);
setOnCancelListener(cancelListener);
mAlert = new AlertController(context, this, getWindow());
}
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title);
}
public void setCustomTitle(View customTitleView) {
mAlert.setCustomTitle(customTitleView);
}
public void setView(View view) {
mAlert.setView(view);
}
void setButtonPanelLayoutHint(int layoutHint) {
mAlert.setButtonPanelLayoutHint(layoutHint);
}
......里面类似的这样的Set方法很多。
public static class Builder {
//存储Dialog布局中的各个参数,如:title,Message等
private final AlertController.AlertParams P;
//经典的Builder模式,通过Builder去构造,把所有的参数都存在P里
Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setCustomTitle(View customTitleView) {
P.mCustomTitleView = customTitleView;
return this;
}
public Builder setMessage(int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
//构建AlertDialog的各个参数
public AlertDialog create() {
//实例化一个AlertDialog对象
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
//将P中参数应用到Dialog.mAlert对象中,在这里面就是第三行的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等,然后将这些参数存在
AlertParams中,AlertParams中包含了许多Dialog的布局参数。在调用create()方法的时候,实例化出一个新的
AlertDialog,并且将Builder成员变量中的参数通过apply方法将参数应用到AlerDialog的alert对象中。当我们
获取AlertDialog对象后,通过show函数就可以显示这个对话框。
我们来看Builder的show方法:
public AlertDialog show() {
AlertDialog dialog = create();
dialog.show();
return dialog;
}
实际调用的是AlertDialog中的show()方法:
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;
//1.没有创建,则调用Dialog的OnCreate方法
if (!mCreated) {
dispatchOnCreate(null);
}
//2.调用Dialog的onStart()方法
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;
}
try {
//将mDecor添加到WindowManager中
mWindowManager.addView(mDecor, l);
mShowing = true;
//发送一个显示Dialog的消息
sendShowMessage();
} finally {
}
}
在show函数中主要做了如下的事情:
(1)通过dispatchOnCreate函数来调用AlertDialog的onCreate函数
(2)然后接着调用onStart函数
(3)最后将Dialog的mDecor添加到WindowManager中(有点不明白这里,为什么要获取到最顶部的布局)
所以视图的创建一定是在onCreate()方法中:
//Dialog中的onCreate方法是个空实现,所以回调的是子类AlertDialog中的onCreate方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
public void installContent() {
//设置窗口的内容布局
final int contentView = selectContentView();
mDialog.setContentView(contentView);
setupView();
}
private void setupView() {
//调用Window对象的setContentView,实际上最终调用Window对象的setContentView函数
//这些参数从AlertController的构造函数中可以看到,布局资源为:alert_dialog.xml
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);
}
}
// 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);
}
}
//如果有listview就初始化
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);
}
}
}
setupView的作用就是用来初始化AlertDialog布局中的各个部分,在调用该函数后整个Dialog的视图
内容设置完毕。而这些区域的视图都属于mAlertDialogLayout的子元素,Window对象又关联了它的
整个布局树,当调用完setupView之后整个视图树的数据填充完毕,当用户调用show函数时,WindowManager
会将Window对象的DecorView添加到用户窗口上,并显示。
总结:在AlerDialog的Builder模式中并没有出现Director角色的出现,正式由于这样才使得整个程序显的简洁。
设计模式是一种思想,不应该去生搬硬套,要灵活使用。
参考书籍:
《Android源码设计模式解析与实战》