您的位置:首页 > 产品设计 > UI/UE

Android源码中的Builder模式实现

2016-01-28 12:58 721 查看
Android源码中,最常用到的Builder模式就是AlertDialog.Builder,使用Builder来构建复杂的AlertDialog对象。在开发中,我们经常用到AlertDialog,具体示例如下:

[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的源码很值得我们去学习。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: