@Beeder
2017-07-06T10:11:48.000000Z
字数 15496
阅读 894
android
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
(1)相同的方法,不同的执行顺序,产生不同的事件结果时。
(2)多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时。
(3)产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用,这个使用建造者模式非常适合。
(4)当初始化一个对象特别复杂时,如参数多,且很多参数有默认值。
UML类图如图3-1所示。
角色介绍:
public interface Builder {
//创建部件A 比如创建汽车车轮
void buildPartA();
//创建部件B 比如创建汽车方向盘
void buildPartB();
//创建部件C 比如创建汽车发动机
void buildPartC();
//返回最后组装成品结果 (返回最后装配好的汽车)
Product getResult();
}
public class ConcreteBuilder implements Builder {
Part partA, partB, partC;
public void buildPartA() {
//这里是具体如何构建partA的代码
};
public void buildPartB() {
//这里是具体如何构建partB的代码
};
public void buildPartC() {
//这里是具体如何构建partB的代码
};
public Product getResult() {
//返回最后组装成品结果
};
}
//Director 类,负责制造
public class Director {
private Builder builder;
public Director( Builder builder ) {
this.builder = builder;
}
// 将部件partA partB partC最后组成复杂对象
//这里是将车轮 方向盘和发动机组装成汽车的过程
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
}
public interface Product { }//产品
public interface Part { }//部件
//调用
ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.construct();
Product product = builder.getResult();
通常一个类拥有多个角色,将构造函数、字段私有化,把Builder写成内部类,通过builder类的setter方法设置私有函数和方法,如此封装用户就只能通过Builder对象设置属性了。
简单实例如下:
//customView类
public class customView {
//创建静态config对象
private int a;
private int b;
//私有构造方法
private customView(){
}
//Builder内部类
public static class Builder{
int a;
int b;
//setter方法,设置config对象参数
public Builder setA(int a){
this.a = a;
return this;
}
public Builder setB(int b){
this.b = b;
return this;
}
void applyConfig(customView view){
view.a=this.a;
view.b=this.b;
}
//返回view
public customView create(){
customView view = new customView();
applyConfig(view);
return view;
}
}
}
用户调用如下:
customView view = new customView.Builder()
.setA(0)
.setB(0)
.create();
在开发过程中,我们经常用到AlertDialog,AlertDialog.Builder就是源码中最常用到的Builder模式,具体示例如下:
private void showDialogs(Context context){
AlertDialog.Builder builder= new AlertDialog.Builder(context);
builder.setIcon(R.drawable.ic_launcher);
builder.setTitle("title");
builder.setMessage("message");
builder.setPositiveButton("Button", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("点击了对话框上的Button");
}
});
builder.create().show();
}
从类名就可以看出这就是一个Builder模式,通过Builder对象组装Dialog的各个部分,如Icon、Title、Message等,将Dialog的构造和表示进行分离。相关源码如下:
//路径:frameworks/base/core/java/android/app/AlertDialog.java
public class AlertDialog extends Dialog implements DialogInterface {
//AlertController 接收Builder成员变量p中的各个参数 实现类
private AlertController mAlert;
//构造函数
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 = new AlertController(getContext(), this, getWindow());
}
//实际上调用的是mAlert的setTitle方法
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title);
}
//实际上调用的是mAlert的setCustomTitle方法
public void setCustomTitle(View customTitleView) {
mAlert的.setCustomTitle(customTitleView);
}
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
//省略
//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 themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
//Builder的其他代码省略
//2、设置各种参数
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(@StringRes int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
public Builder setIcon(Drawable icon) {
P.mIcon = icon;
return this;
}
//3、构建AlertDialog,传递参数
public AlertDialog create() {
// 4、new AlertDialog对象,并且将参数传递给AlertDialog实例dialog
// Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, 0, 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的nAlert对象中,即p.apply(dialog.mAlert)代码段。
apply函数实现如下:
//代码:frameworks/base/core/java/com/android/internal/app/AlertController.java
public class AlertController {
//省略
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);
}
//如果设置了mItems,则表示是单选或多选列表,此时创建一个ListView
// 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);
}
//将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);
}
}
}
}
在apply函数中,只是将AlertParams参数设置到AlertController中。例如,将标题设置到Dialog对应的标题识图中,将Message设置到内容视图中等。当我们获取到AlertDialog对象后,通过show函数就可以显示这个对话框。
AlertDialog中相关代码如下:
//路径:frameworks/base/core/java/android/app/AlertDialog.java
public class AlertDialog extends Dialog implements DialogInterface {
public static class Builder {
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
//将参数设置到AlertController对象dialog中
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;
}
}
}
我们再看看Dialog的show函数(该函数在Dialog类中):
//路径:frameworks/base/core/java/android/app/Dialog.java
/**
* 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() {
if (DBG) {
Log.d(TAG, "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();
//省略
//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添加到mWindowManager中
mWindowManager.addView(mDecor添加到, l);
mShowing = true;
//发送一个显示Dialog的消息
sendShowMessage();
} finally {
}
}
在show函数中主要做了如下几个事情:
很明显,这就是一系列的典型的生命周期函数。按照惯例,AlertDialog的内容视图构建按理应该在onCreate函数中,我们来看看是不是:
//路径:frameworks/base/core/java/android/app/AlertDialog.java
public class AlertDialog extends Dialog implements DialogInterface {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//调用了AlertController的installContent方法
mAlert.installContent();
}
}
在onCreate函数中主要调用了AlertController的installContent方法,Dialog中的onCreate函数只是一个空实现而已,可以忽略他。那么AlertDialog的内容视图必然就在installContent函数中。
//路径:frameworks/base/core/java/com/android/internal/app/AlertController.java
public class AlertController {
public void installContent() {
/* We use a custom title so never request a window title */
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
int contentView = selectContentView();
mWindow.setContentView(contentView);
setupView();
setupDecor();
}
private int selectContentView() {
if (mButtonPanelSideLayout == 0) {
return mAlertDialogLayout;
}
if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
return mButtonPanelSideLayout;
}
// TODO: use layout hint side for long messages/lists
return mAlertDialogLayout;
}
}
installContent函数的代码,但极为重要,它调用了Window对象的setContentView,这个setContentView就与Activity中的一模一样,实际上Activity最终也是调用Window对象的setContentView函数。因此,这里就是设置AlertDialog的内容布局,这个布局就是mAlertDialogLayout的值。这个值在AlertController的构造函数中进行了初始化,具体代码如下:
//路径:frameworks/base/core/java/com/android/internal/app/AlertController.java
public class AlertController {
public 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);
//AlertController的布局id,也就是alert_dialog.xml布局
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
mButtonPanelSideLayout = a.getResourceId(
R.styleable.AlertDialog_buttonPanelSideLayout, 0);
mListLayout = a.getResourceId(
R.styleable.AlertDialog_listLayout, R.layout.select_dialog);
//省略
a.recycle();
}
}
从AlertController的构造函数中可以看到,AlertDialog的布局资源就是select_dialog.xml这个布局,我们直接大致看他的结构如下:
当通过Builder对象的setTitle、setMessage等方法设置具体内容时,就是将这些内容填充到对应的视图中。而AlertDialog也允许你通过setView传入内容视图,这个内容视图就是替换掉图中的内容视图(蓝色区域),AlertDialog预留了一个costomPanel区域用来显示用户自定义的内容视图。我们来看看setupView函数:
public class AlertController {
private void setupView() {
final View parentPanel = mWindow.findViewById(R.id.parentPanel);
final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
//1、获取内容区域
final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
//1、获取按钮
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);
//2、初始化内容
setupContent(contentPanel);
setupButtons(buttonPanel);
//2、初始化标题
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.
final View divider;
if (mMessage != null || mListView != null || hasCustomPanel) {
divider = topPanel.findViewById(R.id.titleDivider);
} else {
divider = topPanel.findViewById(R.id.titleDividerTop);
}
if (divider != null) {
divider.setVisibility(View.VISIBLE);
}
}
// 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) {
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);
}
}
}
这个setupView顾名思义就是初始化AlertDialog布局中的各个部分,如标题区域、按钮区域、内容区域等,在该函数调用之后整个Dialog的视图内容全部设置完毕。而这些各区域的视图都属于mAlertDialogLayout布局中的子元素,Window对象又关联了mAlertDialogLayout的整个布局树,当调用完setupView之后整个视图树的数据都填充完毕。当用户调用了show函数时,WindosManager会将Window对象的DecorView(也就是mAlertDialogLayout对应的视图),添加到用户的窗口上,并且显示出来。至此,整个Dialog就会出现在用户的视野中了。
在AlertDialog的Builder模式中并没有看到Director角色的出现(其实在很多场景中,Android并没有按照《设计模式:可复用面向对象软件的基础》一书中描述的经典模式来实现,而是做了些修改,使得更易于使用)。这里的AlertDialog.Builder同时扮演了builder、ConcreteBuilder、Dirctor的角色,简化了Builder模式的设计。