Builder模式的定义
将一个复杂对象的 构建 与它的 表示 分离,使得同样的构建过程可以创建不同的表示。
Builder模式的使用场景
1.相同的方法,不同的执行顺序,产生不同的结果。
2.多个部件或零件,都可以装配到一个对象中,但产生的运行结果又是不同时。
3.产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用,这个时候适合使用构造者模式。
4.当初始化一个对象特别复杂,如参数多,且很多参数都是默认值时。
Android源码中的Builder模式
public class AlertDialog extends Dialog implements DialogInterface { }
AlertDialog.Builder 通过该Builder来构建复杂的AlertDialog对象。
通过Builder对象组装Dialog的各个部分,如title、buttons、Message等,将Dialog的构造和表示分离。
private AlertController mAlert; //接收Builder成员变量P中的各个参数
//构造函数
protected AlertDialog(Context context, @StyleRes int themeResId) {
this(context, themeResId, true);
}
//构造函数 AlertDialog
AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
//构造AlertController
mAlert = AlertController.create(getContext(), this, getWindow());
}
//实际调用的是mAlert的setTitle方法
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title);
}
/**
* @see Builder#setCustomTitle(View)
*/
//实际上调用的是mAlert的setCustomTitle方法
public void setCustomTitle(View customTitleView) {
mAlert.setCustomTitle(customTitleView);
}
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
Builder为AlertDialog的内部类
AlertController.AlertParams P 中存储了AlertDialig的各个参数。
//Builder为AlertDialog的内部类
public static class Builder {
... ...
}
//储存AlertDialog的各个参数,如title、message、icon等
private final AlertController.AlertParams P;
public Builder(Context context) {
this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
}
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
下述代码中,Builder类可以设置AlertDialog中的title、message、button等参数,这些参数都存储在类型为AlertController.AlertParams的成员变量P中,AlertController.AlertParams包含了与AlertDialog视图中对应的成员变量。在调用Builder类的create函数时会创建AlertDialog,并且将Builder成员变量P中保存的参数应用到AlertDialog的mAlert对象中,即P.apply(dialog.mAlert)代码段。
//设置各种参数
/**
* Set the title displayed in the {@link Dialog}.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
/**
* Set the message to display.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
public Builder setView(View view) {
P.mView = view;
P.mViewLayoutResId = 0;
P.mViewSpacingSpecified = false;
return this;
}
//构造AlertDialog,传递参数
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
//调用new AlertDialog构造对象,并且将参数传递给个体AlertDialog。
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
//将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)代码段。
#AlertController
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
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);
}
*/
}
AlertParams是AlertController 的静态内部类
#AlertParams
在apply函数中,只是将AlertParams参数设置到AlertController中,例如将标题设置到Dialog对应的标题视图中等。当我们获取到AlertDialog对象后,通过show函数就可以显示这个对话框。
public static class 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);
}
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对应的标题视图中等。当我们获取到AlertDialog对象后,通过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>
*/
#AlertDialog.Builder
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
Dialog的show函数如下:
public class AlertDialog extends Dialog implements DialogInterface {
... ...
}
/**
* 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}.
*/
#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);
} 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();
//获取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);
}
//获取布局参数
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;
}
//将mDecor添加到WindowManager中
mWindowManager.addView(mDecor, l);
mShowing = true;
//发送一个显示Dialog的消息
sendShowMessage();
}
在show函数中主要做如下几件事:
1.通过dispatchOnCreate函数来调用AlertDialog的onCreate函数。
2.然后调用AlertDialog的onStart函数。
3.最后将Dialog的DecorView添加到WindowManager中。
这是一系列典型的生命周期函数。按照惯例,AlertDialog的内容视图构建应该在onCreate函数中。
AlertDialog的onCreate函数:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 调用了AlertController的installContent方法
mAlert.installContent();
}
在onCreate函数中主要调用了AlertController的installContent方法,Dialog中的onCreate函数只是一个空实现。AlertDialog的内容视图必然在installContent函数中。
#AlertController
public void installContent() {
//设置窗口没有title
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
int contentView = selectContentView();
//设置窗口的内容视图布局
mWindow.setContentView(contentView);
//初始化AlertDialog的其他子视图的内容
setupView();
}
installContent函数的代码很少,但极为重要,它调用了Window对象的setContentView,这个setContentView就与Activity中的一样,实际上Activity最终也是调用Window对象的setContentView函数。这里是设置AlertDialog的内容布局,这个布局就是mAlertDialogLayout字段值。这个值在AlertController的构造函数中初始化。
#AlertController
protected AlertController(Context context, DialogInterface di, Window window) {
mContext = context;
mDialogInterface = di;
mWindow = window;
mHandler = new ButtonHandler(di);
final TypedArray a = context.obtainStyledAttributes(null,
R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
// modify begin by sunliang for AlertDialog 20170721
useGomeTheme = a.getBoolean(R.styleable.AlertDialog_useGomeTheme, false);
if(useGomeTheme){
mAlertDialogLayout = a.getResourceId(
com.gome.R.styleable.AlertDialog_layout, com.gome.R.layout.gome_alert_dialog);
} else {
//AlertDialog的布局id,也就是alert_dialog.xml布局
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
}
if(useGomeTheme) {
mButtonPanelSideLayout = a.getResourceId(
com.gome.R.styleable.AlertDialog_buttonPanelSideLayout, 0);
} else {
mButtonPanelSideLayout = a.getResourceId(
R.styleable.AlertDialog_buttonPanelSideLayout, 0);
}
if(useGomeTheme) {
mListLayout = R.layout.select_dialog_gome;
} else {
mListLayout = a.getResourceId(
R.styleable.AlertDialog_listLayout, R.layout.select_dialog);
}
if(useGomeTheme) {
mMultiChoiceItemLayout = a.getResourceId(
com.gome.R.styleable.AlertDialog_multiChoiceItemLayout,
com.gome.R.layout.gome_select_dialog_multichoice);
} else {
mMultiChoiceItemLayout = a.getResourceId(
R.styleable.AlertDialog_multiChoiceItemLayout,
R.layout.select_dialog_multichoice);
}
if(useGomeTheme) {
mSingleChoiceItemLayout = a.getResourceId(
com.gome.R.styleable.AlertDialog_singleChoiceItemLayout,
com.gome.R.layout.gome_select_dialog_singlechoice);
} else {
mSingleChoiceItemLayout = a.getResourceId(
R.styleable.AlertDialog_singleChoiceItemLayout,
com.gome.internal.R.layout.gome_select_singlechoice);
}
if(useGomeTheme) {
mListItemLayout = a.getResourceId(
com.gome.R.styleable.AlertDialog_listItemLayout,
com.gome.R.layout.gome_select_dialog_item);
} else {
mListItemLayout = a.getResourceId(
R.styleable.AlertDialog_listItemLayout,
R.layout.select_dialog_item);
}
//modify end by sunliang for AlertDialog 20170721
mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
a.recycle();
/* We use a custom title so never request a window title */
//window.requestFeature(Window.FEATURE_NO_TITLE);
}
当通过Builder对象的setTitle、setMessage等方法设置具体内容时,就是将这些内容填充到对应的视图中。而AlertDialog也允许你通过setView传入内容视图,这个内容视图就是替换掉蓝色区域。AlertDialog预留了一个costomPanel区域用来显示用户自定义的内容视图。
private void setupView() {
//modify begin by sunliang for AlertDialog 20170721
// 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);
View parentPanel;
//获取并初始化title区域
if(useGomeTheme) {
parentPanel = mWindow.findViewById(com.gome.R.id.parentPanel);
} else {
parentPanel = mWindow.findViewById(R.id.parentPanel);
}
View defaultTopPanel;
if(useGomeTheme) {
defaultTopPanel = parentPanel.findViewById(com.gome.R.id.topPanel);
} else {
defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
}
View defaultContentPanel;
if(useGomeTheme) {
defaultContentPanel = parentPanel.findViewById(com.gome.R.id.contentPanel);
} else {
defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
}
View defaultButtonPanel;
// 获取并初始化内容区域
if(useGomeTheme) {
defaultButtonPanel = parentPanel.findViewById(com.gome.R.id.buttonPanel);
} else {
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);
ViewGroup customPanel;
if(useGomeTheme) {
customPanel = (ViewGroup) parentPanel.findViewById(com.gome.R.id.customPanel);
} else {
customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
}
setupCustomContent(customPanel);
View customTopPanel;
if(useGomeTheme) {
customTopPanel = customPanel.findViewById(com.gome.R.id.topPanel);
} else {
customTopPanel = customPanel.findViewById(R.id.topPanel);
}
View customContentPanel;
if(useGomeTheme) {
customContentPanel = customPanel.findViewById(com.gome.R.id.contentPanel);
} else {
customContentPanel = customPanel.findViewById(R.id.contentPanel);
}
View customButtonPanel;
if(useGomeTheme) {
customButtonPanel = customPanel.findViewById(com.gome.R.id.buttonPanel);
} else {
customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
}
//modify end by sunliang for AlertDialog 20170721
// 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);
}
}
mWindow.setCloseOnTouchOutsideIfNotSet(true);
}
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 = topPanel.findViewById(R.id.titleDivider);
}
} else {
divider = topPanel.findViewById(R.id.titleDividerTop);
}
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 ? View.SCROLL_INDICATOR_TOP : 0)
| (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
content.setScrollIndicators(indicators,
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
}
}
final TypedArray a = mContext.obtainStyledAttributes(
null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
//设置背景
setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
hasTopPanel, hasCustomPanel, hasButtonPanel);
a.recycle();
}
}
private void setupCustomContent(ViewGroup customPanel) {
final View customView;
如果用户设置了内容视图,那么将它显示在customPanel的custom布局里面
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) {
//modify begin by sunliang for AlertDialog 20170721
// final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
FrameLayout custom;
if(useGomeTheme) {
custom = (FrameLayout) mWindow.findViewById(com.gome.R.id.custom);
} else {
custom = (FrameLayout) mWindow.findViewById(R.id.custom);
}
//modify end by sunliang for AlertDialog 20170721
//显示用户设置的视图
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);
}
}
setupView顾名思义就是初始化AlertDialog布局的各个部分,在该函数调用之后,整个Dialog的视图内容全部设置完毕。这些各区域的视图都属于mAlertDialogLayout布局中的子元素,Window对象又关联了mAlertDialogLayout的整个布局树,调用完setupView之后整个视图树的数据填充完毕,当用户调用show函数时,WindowManager将会将Window对象的DecorView添加到用户窗口上。
注意AlertDialog的setView方法和setContentView方法:
setView方法需要在show方法之前调用。setView是AlertDialog的方法,在AlertDialog中调用AlertController中的setView。而在AlertController中这个setView则是指的CustomView的部分而不是整个窗体。
注意AlertDialog的setView方法和setContentView方法:
setView方法需要在show方法之前调用。setView是AlertDialog的方法,在AlertDialog中调用AlertController中的setView。而在AlertController中这个setView则是指的CustomView的部分而不是整个窗体。
setContentView实际调用的是PhonWindow的setContentView方法,其设置的是整个窗口的布局。
参考《android源码设计模式》