您的位置:首页 > 移动开发 > Android开发

Android进阶系列之源码分析AlertDialog建造者模式

2016-12-29 16:54 429 查看
 *本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家发布


建造者模式之前也写了一篇学习笔记,不过那只是很简单的运用,要去看源码,要去看源码还是得在撸一遍设计模式才行啊。不能怂就是干。



建造者模式,在于分工明确,一个抽象建造者,一个指挥者,一个具体的建造者,当然还需要具体的产品。那么我们以一个软件产品为例。技术主管就是抽象建造者,他和产品经理沟通,知道要做一个什么样的产品。而程序猿就是苦逼的体力劳动者,技术主管说咋做你就咋做。而指挥者就是公司的产品经理,负责和用户沟通,了解客户的需求。
那么基本的套路有了,开始撸一个简单的例子出来。

public class Product {
public static final int ANDROID = 0;
public static final int IOS = 1;

private String appName;
private String appFuction;
private int appSystem;

public String getAppName() {
return appName;
}

public void setAppName(String appName) {
this.appName = appName;
}

public String getAppFuction() {
return appFuction;
}

public void setAppFuction(String appFuction) {
this.appFuction = appFuction;
}

public int getAppSystem() {
return appSystem;
}

public void setAppSystem(int appSystem) {
this.appSystem = appSystem;
}
}
这是产品,有app名称,app的功能,和app的操作系统。在来一个技术主管

/**
* 技术主管
* Created by Administrator on 2016/12/29.
*/

public abstract class TechManager {
public abstract TechManager setAppName(@NonNull String appName);
public abstract TechManager setAppFuction(@NonNull String appFuction);
public abstract TechManager setAppSystem(@AppSystem int appSystem);
public abstract Product build();
}
在传入的系统时,我用了一个自定义注解,只能传入规定的参数,详情见:http://blog.csdn.net/sw5131899/article/details/53842362
/**
*程序猿。IOS和ANDROID,后台都会的全栈选手
* Created by Administrator on 2016/12/29.
*/

public class Progremer extends TechManager{

private Product product;
private InnerProduct innerProduct = new InnerProduct();

@Override
public TechManager setAppName(@NonNull String appName) {
innerProduct.setAppName(appName);
return this;
}

@Override
public TechManager setAppFuction(@NonNull String appFuction) {
innerProduct.setAppFuction(appFuction);
return this;
}

@Override
public TechManager setAppSystem(@AppSystem int appSystem) {
innerProduct.setAppSystem(appSystem);
return this;
}

private class InnerProduct{
private String appName;
private String appFuction;
private int appSystem;

public String getAppName() {
return appName;
}

public void setAppName(String appName) {
this.appName = appName;
}

public String getAppFuction() {
return appFuction;
}

public void setAppFuction(String appFuction) {
this.appFuction = appFuction;
}

public int getAppSystem() {
return appSystem;
}

public void setAppSystem(int appSystem) {
this.appSystem = appSystem;
}
}

@Override
public Product build() {
product = new Product();
product.setAppName(innerProduct.getAppName());
product.setAppFuction(innerProduct.getAppFuction());
product.setAppSystem(innerProduct.getAppSystem());
return product;
}
}
这里采用了一下建造者模式的变异,这样其实更有利于使用。至于为什么,待会再产品经理那使用就知道了。

/**
* 产品经理
* Created by Administrator on 2016/12/29.
*/

public class ProductManager {
public static Product create(@AppSystem int system){
return new Progremer().setAppSystem(system).setAppName("探探").setAppFuction("划一划,找妹子。").build();
}
}
在这里,采用链式调用,非常方便。这也是Android在源码经常使用的一种模式。

在客户类里,只需要持有产品经理类之后,就可以得到产品了。

public class Client {
public void main(String[] args){
//客户:我需要一个可以摇一摇找妹子的软件
//产品经理:分析得出那就做一个探探吧
//技术主管:appName:探探  系统:ios,android 功能:摇一摇,找妹子
Product android = ProductManager.create(Product.ANDROID);
Product ios = ProductManager.create(Product.IOS);
}
}
建造者模式也就这么多了。其实变异来的建造者可以只需要具体建造者,抽象的不要了。指挥者也可以不要了。

public class Client {
public void main(String[] args){
//客户:我需要一个可以摇一摇找妹子的软件
//产品经理:分析得出那就做一个探探吧
//技术主管:appName:探探  系统:ios,android 功能:摇一摇,找妹子
Product android = ProductManager.create(Product.ANDROID);
Product ios = ProductManager.create(Product.IOS);
//程序猿觉得太累了,工资又少,干的最多。最后决定自己出去单干。
Progremer niubiProgremer = new Progremer();

Product androidBest = niubiProgremer.setAppName("探探").setAppSystem(Product.ANDROID).setAppFuction("摇一摇,找妹子").build();
Product iosBest = niubiProgremer.setAppName("探探").setAppSystem(Product.IOS).setAppFuction("摇一摇,找妹子").build();
}
}
到这里,有没有觉得眼熟啊,AlertDialog调用也是这样一大串,链式调度。非常方便。那么我开始撸AlertDialog的源码吧。

Android源码这里我给个百度云的连接,大家需要的自行下载。链接:http://pan.baidu.com/s/1nv8prJj 密码:5mfz
只用AlertDialog的时候,都知道。

AlertDialog.Builder builder = new AlertDialog.Builder(this);
一看就知道创建AlertDialog时,需要通过内部类Builder来创建。那么我们来看一下Builder里有什么呢。AlertDailog就是建造者模式中指挥者的角色。

public static class Builder {
private final AlertController.AlertParams P;
private final int mTheme;

public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}

public Builder(@NonNull Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
mTheme = themeResId;
}

@NonNull
public Context getContext() {
return P.mContext;
}

public Builder setTitle(@StringRes int titleId) {
P.mTitle = P.mContext.getText(titleId);
return this;
}

public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}

........

public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
P.mPositiveButtonText = P.mContext.getText(textId);
P.mPositiveButtonListener = listener;
return this;
}

public AlertDialog create() {

final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
P.apply(dialog.mAlert);//这里调用了apply真正创建了需要显示的dialog,也就是说之前的设置都是以P做一个数据缓存
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;
}

public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
}
我把源码整理了一下,觉得有用的展示出来。在Builder创建了一个AlertController.AlertParams P;P是后面所有设置方法所调用的对象。那么我们再看看AlertController.AlertParams这个内部类有什么参数。

public static class AlertParams {
public final Context mContext;
public final LayoutInflater mInflater;

public int mIconId = 0;
public Drawable mIcon;
public int mIconAttrId = 0;
public CharSequence mTitle;
public View mCustomTitleView;
public CharSequence mMessage;
public CharSequence mPositiveButtonText;

........

public AlertParams(Context context) {
mContext = context;
mCancelable = true;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

public void apply(AlertController dialog) {//传入一个dialog,获取AlertParams缓存的数据。
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);
}
// 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);
}
*/
}

......
}


这里最主要的方法时apply(),在这里AlertParams 的所有属性转给了一个AlertController dialog。那么我们看看AlertController 是什么。

class AlertController {
private final Context mContext;
final AppCompatDialog mDialog;
private final Window mWindow;

private CharSequence mTitle;
private CharSequence mMessage;
ListView mListView;
private View mView;

private int mViewLayoutResId;

private int mViewSpacingLeft;
private int mViewSpacingTop;
private int mViewSpacingRight;
private int mViewSpacingBottom;
private boolean mViewSpacingSpecified = false;

Button mButtonPositive;
private CharSequence mButtonPositiveText;
Message mButtonPositiveMessage;

Button mButtonNegative;
private CharSequence mButtonNegativeText;
Message mButtonNegativeMessage;

Button mButtonNeutral;
private CharSequence mButtonNeutralText;
Message mButtonNeutralMessage;

NestedScrollView mScrollView;

private int mIconId = 0;
private Drawable mIcon;

private ImageView mIconView;
private TextView mTitleView;
private TextView mMessageView;
private View mCustomTitleView;

ListAdapter mAdapter;

int mCheckedItem = -1;

private int mAlertDialogLayout;
private int mButtonPanelSideLayout;
int mListLayout;
int mMultiChoiceItemLayout;
int mSingleChoiceItemLayout;
int mListItemLayout;

private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE;

Handler mHandler;

..........
}




AlertController.AlertParams 持有AlertController的所有属性,在调用builder里的设置属性方法时,就是给AlertController.AlertParams做一个缓存。在调用了builder 的show方法之后。里面在调用具体dialog的show方法显示弹窗。
那么AlertDialog在建造者模式中担任的是指挥者,Bilder就是具体的建造者。采用了链式调用。AlertController是产品,而AlertController.AlertParams是产品的缓存。比如我调用了两次setTitle(),在缓存时后一次会覆盖前一次,这样就解决了开发者冲动调用的问题。最后不论是调用Builder的show方法,还是调用调用AlertDialog的show方法。都是允许的。
比如:

new AlertDialog.Builder(this).setTitle("标题").setIcon(R.mipmap.ic_launcher).setMessage("测试").show();


这样是可以的,最后调用的是Builder的show方法,在Builder的show方法中,调用了AlertDialog的create()得到缓存数据的AlertDialog进行显示。

new AlertDialog.Builder(this).setTitle("标题").setIcon(R.mipmap.ic_launcher).setMessage("测试").create().show();

这样也是可以的,手动调用create()方法,之后在调用AlertDialog的show方法进行显示。

那么这样呢?

new AlertDialog.Builder(this).setTitle("标题").setIcon(R.mipmap.ic_launcher).setMessage("测试").show().show();

第一个show是builder的show,里面创建了AlertDialog并且调用了AlertDialog的show方法。那么第二个show也是AlertDialog的show方法,会重复调用么?答案肯定是不可能的。看看Dialog的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;
}

.....

try {
mWindowManager.addView(mDecor, l);
mShowing = true;

sendShowMessage();
} finally {
}
}


在调用底层的show方法时,会先进行一次判断,第一次show之后mShowing已经设为true。那么第二次调用时,判断到已经显示,就不会再次调用绘制逻辑(省略号部分)。

那么建造者模式就到这儿了,源码的博大精深真是令人向往。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息