Android源码中的Builder模式实现
2016-01-28 12:58
721 查看
Android源码中,最常用到的Builder模式就是AlertDialog.Builder,使用Builder来构建复杂的AlertDialog对象。在开发中,我们经常用到AlertDialog,具体示例如下:
显示结果如下图:
从类名就可以看出这就是一个Builder模式,通过Builder对象来组装Dialog的各个部分,如title、buttons、Message等,将Dialog的构造和表示进行分离。下面看看AlertDialog的相关源码:
上述代码中,Builder类可以设置AlertDialog中的title、message、button等参数,这些参数都存储在类型为AlertController.AlertParams的成员变量中P中,AlertController.AlertParams中包含了与AlertDialog视图中对应的成员变量。在调用Builder类的create函数时会创建AlertDialog,并且Builder成员变量P中保存的参数应用到AlertDialog的mAlert对象中,即P.apply(dialog.mAlert)代码。我们在看看apply函数的实现:
在apply函数中,只是将AlertParams参数设置到AlertController中,例如,将标题设置到Dialog对应的标题视图中,将Message设置到内容视图中等。当我们获取到AlertDialog对象后,通过show函数就可以显示这个对话框。我们看看Dialog的show函数(该函数在Dialog类中):
在show函数中主要做了如下几个事情:
1. 通过dispatchOnCreate函数来调用AlertDialog的onCreate函数;
2. 然后调用AlertDialog的onStart函数;
3. 最后将Dialog的DecorView添加到WindowManager中。
很明显,这就是一系列典型的声明周期函数。那么按照惯例,AlertDialog的内容视图构建按理应该在onCreate函数中,我们来看看是不是:
在onCreate函数中主要调用了AlertController的installContent方法,Dialog中的onCreate函数只是一个空实现而已,可以忽略它。那么AlertDialog的视图内容必然救灾installContent函数中,继续深入了解吧:
installContent函数的代码很少,但极为重要,它调用了Window对象的setContentView,这个setContentView就与Activity中的一模一样,实际上Activity最终也是调用Window对象的setContentView函数。因此,这里就是设置AlertDialog的内容布局,这个布局就是mAlertDialogLayout字段的值,这个值在AlertController的构造函数中进行了初始化,具体代码如下:
从AlertController的构造函数中可以看到,AlertDialog的布局资源就是alert_dialog.xml这个文件,其效果图如下:
当通过Builder对象setTitle、setMessage等方法设置具体内容时,就是将这些内容填充到对应的视图中。而AlertDialog也允许你通过setView传入内容视图,这个内容视图就是替换掉上图中的Message区域,AlertDialog预留了一个costomPanel区域用来显示用户自定义内容视图。我们来看看setupView函数:
这个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的源码很值得我们去学习。
[code] /** * 显示基本的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的相关源码:
[code]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函数的实现:
[code]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类中):
[code] 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_N***IGATION) == 0) { WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); nl.copyFrom(l); nl.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_N***IGATION; 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函数中,我们来看看是不是:
[code]@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 调用了AlertController的installContent方法 mAlert.installContent(); }
在onCreate函数中主要调用了AlertController的installContent方法,Dialog中的onCreate函数只是一个空实现而已,可以忽略它。那么AlertDialog的视图内容必然救灾installContent函数中,继续深入了解吧:
[code]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的构造函数中进行了初始化,具体代码如下:
[code]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函数:
[code]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的源码很值得我们去学习。
相关文章推荐
- iOS开发之UILabel动态高度设置方法
- Codeforces Round #257 (Div. 2) B. Jzzhu and Sequences
- iOS学习之路-简易”IT之家“(UITableView)
- confluence5.6安装
- Java Request 获取域名
- SVN问题:Server sent unexpected return value (403 Forbidden) in response to OPTIONS
- quick + nimble 单元测试
- 帮你评价UI设计作品好坏的八个标准
- C#利用webclient和webrequest来访问网页(已测webclient)
- 如何在onCreate时拿到UI的大小
- 通讯录编辑联系人(将UITextfield已有的电话号码,或名成)默认显示在编辑的界面
- 设计模式(三)----- 建造者模式(Builder)----(JAVA版)
- UITableViewRowAction -- UITableView的一些小技巧
- UIResponder简介
- 在UITableView的Section Footer添加按钮
- Android异步处理三:Handler+Looper+MessageQueue深入详解
- Android异步处理二:使用AsyncTask异步更新UI界面
- 让UITableView的section header view不悬停,让UITableView的section headerview取消粘性
- iOS开发系列--UITableView全面解析
- Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面