安卓设计模式(三)Builder模式

Builder模式也叫建造者模式,属于创建性模式,一般用于复杂对象的创建
该模式可以将构建复杂对象的过程和它的部件解耦,使得构建过程和部件的表示隔离开来

该系列其他文章:

Android中的使用场景

  • 复杂对象的创建,内部包含多个部件或者零件,都可以装配到一个对象中.如AlertDialog.Builder()

      new AlertDialog.Builder(this)
              .setPositiveButton("确定", null)
              .setTitle("请求权限")
              .setCancelable(false)
              .setMessage(messageResId)
              .show();
    
  • 用于框架的初始化,初始化之后无法对框架内部数据再做改动.如FileDownloader框架的初始化:

      FileDownloadConfiguration.Builder builder = new FileDownloadConfiguration.Builder(this);
      builder.configFileDownloadDir(StorageUtils.getFilesDirectory(mContext).getAbsolutePath() + File.separator + "pdf");
      builder.configRetryDownloadTimes(2);
      FileDownloadConfiguration configuration = builder.build();
      FileDownloader.init(configuration);
    
  • 创建或初始化对象时,参数多,并且参数都具有默认值.

用法

这里通过一个具体需求,一步一步来设计Builder模式

底部这个弹出框在很多页面会用到,我们就需要封装一下,叫做MenuDialog,这里使用Dialog来做(也可以用Popwindow),分析这个dialog需求,特点如下:

  • 从底部弹出,上面一行为分享组件(ShareMenu),是固定的,用于分享
  • 第二行为一些具体操作的按钮(ActionMenu),并且第二行有时候是不需要的(隐藏)
  • 考虑到扩展性,ActionMenu的个数和每一个的图标和提示语(msg)应该是可定制的
  • ShareMenu的点击事件就是调起分享,可以统一处理,ActionMenu由于具体操作不同,应该暴露给调用者自行处理
  • ActionMenu的图标和msg都应该有默认,"分享到"这个title也应该有默认并且可定制

数据存放 MenuDiaControl

在builder模式中,一般会有个存放数据的类Control,这里新建MenuDiaControl用于存放需要用到的参数

public class MenuDiaControl {
    private Context mContext;
    private String title = "分享到";
    //share
    private String mShareTitle = "";
    private String mShareContent = "";
    private String mShareImageUrl = "";
    private String mShareUrl = "";
    //action
    private boolean mHineActionAll = false;//是否隐藏action操作栏
    private List<Boolean> mBooleanList = new ArrayList<>();//单个action的按钮的图标和msg
    private MoreMenuClickListener mListener;//Action回调
    //geter and seter
}

内部类 Builder

Builder类是Builder模式中的主体操作类,接受配置参数并最后生成对象,具体实现如下:

public static class Builder {
    private final MenuDiaControl mDiaControl;//存放参数

    public Builder(Context context) {
        mDiaControl = new MenuDiaControl(context);//初始化Control
    }

    /*<====================================公开的配置方法====================================================>*/

    /**
     * 设置title 默认="分享到"
     *
     * @param title
     * @return
     */
    public Builder title(String title) {
        mDiaControl.setTitle(title);
        return this;
    }
    //更多...(具体代码在文章最后)

    /*<====================================公开的配置方法===================================================>*/

    public MenuDialog build() {//生成对象
        return new MenuDialog(mDiaControl);
    }

}

对象 MenuDialog

即我们需要通过Builder模式创建的对象,最后的产出

public class MenuDialog extends Dialog {

//ButterKnife.bind..

private ShareUtils mShare;
private MenuDiaControl mControl;
private List<TextView> mViewList = new ArrayList<>();

private MenuDialog(MenuDiaControl control) {
    this(control.getContext(), R.style.CustomDialog);
    mControl = control;
    init();
}

private MenuDialog(Context context, int themeResId) {
    super(context, themeResId);
}

private void init() {    //setContentView
    View diaView = View.inflate(mControl.getContext(), R.layout.dialog_post_operator, null);
    setContentView(diaView);
    ButterKnife.bind(this);
    initWindow();
    setContentView(diaView);
    initView();
    mShare = new ShareUtils(mControl.getContext());        //分享工具类
}

private void initView() {        //根据Control中的参数,为对象设置各种属性
    mTvTitle.setText(mControl.getTitle());
    mViewList.add(mTvBackCircleList);
    mViewList.add(mTvCopyUrl);
    mViewList.add(mTvReport);
    mViewList.add(mTvDelete);
    for (int i = 0; i < mViewList.size(); i++) {    //设置单个action
        if (mControl.getBooleanList().get(i)) mViewList.get(i).setVisibility(View.INVISIBLE);
        TextView textView = mViewList.get(i);
        MenuBean menuBean = mControl.getMenuBeanList().get(i);
        textView.setText(menuBean.getMsg());
        Drawable drawable = mControl.getContext().getResources().getDrawable(menuBean.getIconRes());
        drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
        textView.setCompoundDrawables(null, drawable, null, null);
    }
    mLlMore.setVisibility(mControl.isHineActionAll() ? View.GONE : View.VISIBLE);//是否隐藏ActionMenu

}

public static class Builder {
    //Builder内部类...
}

private void initWindow() {    //设置window的一些属性
    setCancelable(false);
    Window window = getWindow();
    int width = LinearLayout.LayoutParams.MATCH_PARENT;
    window.setLayout(width, LinearLayout.LayoutParams.WRAP_CONTENT);
    window.setGravity(Gravity.BOTTOM);
    setCanceledOnTouchOutside(true);
}

@OnClick({R.id.tv_wechat, R.id.tv_wechat_circle, R.id.tv_qq, R.id.tv_sina, R.id.tv_back_circle_list, R.id.tv_copy_url, R.id
        .tv_report, R.id.tv_delete, R.id.btn_cancel})
public void onClick(View view) {
    switch (view.getId()) {
        case R.id.tv_wechat:
            mShare.initShare(mControl.getShareTitle(), mControl.getShareContent(), mControl.getShareImageUrl(), mControl.getShareUrl
                    (), null);
            mShare.setPlatform(Wechat.NAME);
            mShare.startShare();
        
            break;
        //...执行统一的分享操作
        case R.id.tv_back_circle_list:    //Action具体操作回调给调用者
            if (mControl.getListener() != null)
                mControl.getListener().menuClick(0, this);
            break;
        case R.id.tv_copy_url:
            if (mControl.getListener() != null)
                mControl.getListener().menuClick(1, this);
            break;
        case R.id.tv_report:
            if (mControl.getListener() != null)
                mControl.getListener().menuClick(2, this);
            break;
        case R.id.tv_delete:
            if (mControl.getListener() != null)
                mControl.getListener().menuClick(3, this);
            break;
        case R.id.btn_cancel:
            break;
    }
    dismiss();
}
}  

使用

ok,一切都搞定后,我们看下使用方法,跟AlerterDialog的使用很像吧.我们使用Builder模式封装Menudialog,使用简单,链式调用,支持定制,在这个项目中是通用的,满足了上面的需求.

    new MenuDialog.Builder(this)
            .hideActionAll(false)//不隐藏ActionMenu
            .shareData(new ShareBean(mInfoItem.getTitle(), mInfoItem.getSummary(), mInfoItem.getTitlePic(), mInfoItem.getUrl()))//设置分享数据
            .setActionMenu(2,R.mipmap.icon,"msg")//制定ActionMenu
            .title("设置title")
            .hideAction4pos(3)//隐藏单个Action
            .build()
            //...
            .show();

总结

Builder模式设计起来很简单,大家可以大胆的用到自己的项目或者框架中.

  • Control类不是必须的,参数可以直接存放在Builder中,Android中很多Builder模式是省略Control的
  • 上面的R.style.CustomDialog主要是用来使Dialog全屏的
  • 上面的ShareBean是分享需要的参数实体
  • ShareMenu也可以提供分享是否成功的回调,提供定制,等等待完善功能...

具体:

<style name="CustomDialog" parent="@android:style/Theme.Dialog">
    <item name="android:windowFrame">@null</item>
    <!-- Dialog的windowFrame框为无 -->
    <item name="android:windowIsFloating">true</item>
    <!-- 是否浮现在activity之上 -->
    <item name="android:windowIsTranslucent">false</item>
    <!-- 是否半透明 -->
    <item name="android:windowNoTitle">true</item>
    <!-- 背景透明-->
    <item name="android:windowBackground">@color/transparent</item>
    <item name="android:backgroundDimEnabled">true</item>
</style>`

Builder类具体代码:

public static class Builder {
    private final MenuDiaControl mDiaControl;

    public Builder(Context context) {
        mDiaControl = new MenuDiaControl(context);
    }

    /*<========================================================================================>*/
    /**
     * 设置title 默认="分享到"
     *
     * @param title
     * @return
     */
    public Builder title(String title) {
        mDiaControl.setTitle(title);
        return this;
    }

    /**
     * 是否隐藏第二行的扩展操作按钮
     * 默认不隐藏
     *
     * @param hide
     * @return
     */
    public Builder hideActionAll(boolean hide) {
        mDiaControl.setHineActionAll(hide);
        return this;
    }

    public Builder shareTitle(String title) {
        mDiaControl.setShareTitle(title);
        return this;
    }

    public Builder shareContent(String shareContent) {
        mDiaControl.setShareContent(shareContent);
        return this;
    }

    public Builder shareImageUrl(String shareImageUrl) {
        mDiaControl.setShareImageUrl(shareImageUrl);
        return this;
    }

    public Builder ShareUrl(String ShareUrl) {
        mDiaControl.setShareUrl(ShareUrl);
        return this;
    }

    /**
     * 一次性设置分享需要的数据
     *
     * @param bean
     * @return
     */
    public Builder shareData(ShareBean bean) {
        mDiaControl.setShareTitle(bean.getTitle());
        mDiaControl.setShareContent(bean.getContent());
        mDiaControl.setShareImageUrl(bean.getImageUrl());
        mDiaControl.setShareUrl(bean.getUrl());
        return this;
    }

    /**
     * 设置底部某个menu的图标和msg
     *
     * @param position
     * @param iconRes
     * @param msg
     * @return
     */
    public Builder setActionMenu(int position, int iconRes, String msg) {
        if (position < 0 || position > 3) return this;
        mDiaControl.getMenuBeanList().set(position, new MenuBean(iconRes, msg, false));
        return this;
    }

    /**
     * 隐藏底部某个menu
     *
     * @param position
     * @return
     */
    public Builder hideAction4pos(int position) {
        if (position < 0 || position > 3) return this;
        mDiaControl.getBooleanList().set(position, true);
        return this;
    }

    /**
     * 底部menu的点击回调
     *
     * @param listener
     * @return
     */
    public Builder addActionMenuClick(MenuDiaControl.MoreMenuClickListener listener) {
        mDiaControl.setListener(listener);
        return this;
    }

    /*<========================================================================================>*/

    /**
     * 构造器
     *
     * @return
     */
    public MenuDialog build() {
        return new MenuDialog(mDiaControl);
    }
}

关于作者

推荐阅读更多精彩内容