Android设计模式 Builder模式的分析与实践

之前实现了一个Demo底部弹出框,是用DialogFragment实现的一个Dialog,虽然实现了链式调用,但是没有使用Builder模式,所以想试试Builder模式,写博客记录一下。

这篇博客谈论的Android中的Builder模式没有Java传统的使用那么复杂,只是一种编程思路,使需要复杂的参数构建的对象变得简单,而且内部封装构建过程可以方便变换和复用,更是降低了耦合性。

分析 AlertDialog

AlertDialog是谷歌原生的对话框,在V7包中提供了Material Design设计风格的Dialog,使用非常广泛。但是,在这篇博客里,我们只讨论分析它的编程实现模式,它使用的就是Builder模式。

1.先来看看 AlertDialog 源码

AlertDialog源码中可以发现成员变量中有一个AlertController,从它的命名 可以了解它大概是个组织类,即是所谓的”控制层“;再从AlertDialog的构造方法中发现,它没做什么太多的操作,主要就是初始化了AlertController,那我们可以进一步了解到 AlertController可能是担任组织数据逻辑的作用。

那我们就进一步的看看

往下阅读它的源码,AlertDialogonCreate中与AlertController建立了联系,从它的方法命名installContent()可以了解它是初始化AlertDialog内容实体的,那我们可以确定刚刚的推测,AlertController是负责AlertDialog组织内容逻辑的,而AlertDialog只是简单的”UI层“。

再往下就是AlertDialog中静态内部类,也是我们要说的重点Builder

Builder类中发现了一个眼熟的东西——AlertController.AlertParams,从它的命名(命名果然好重要)可以发现它可能是逻辑类,也就是”模型层“(是不是有点像MVC = =),负责处理数据逻辑的。

再看一看源码

跟推测的一样,直接把数据赋予给了AlertController.AlertParams,而且都是return Builder 对象,保证了链式调用;源码里面大部分都是类似的代码,这里只贴出部分。

在源码的最后,发现了重点

create()方法中的apply()是将AlertDialog中的AlertControllerAlertController.AlertParams建立联系,其实就是控制层与逻辑层相通,最后会由 AlertController控制要显示的视图内容。

AlertDialog的源码基本上我们过了一遍,了解它的模式思路,那我们再从apply()进去,看看AlertControllerAlertController.AlertParams是怎么建立联系的。

2.AlertController.AlertParams源码

从代码中可以看见,AlertController获得了AlertController.AlertParams中保存的数据,其他代码不在详述,无非就是转换数据,最后还是要赋予AlertController

3.AlertController 源码

在构造方法中获得相对应的视图。

这是之前介绍过的初始化实体内容的方法,显然是负责构建视图的。

构建视图的过程,通过获得的数据构建相应的视图内容。 所以说AlertController是整个模式中负责组织建造的,这也是Builder模式的核心。

通过分析AlertDialog的源码,我们了解谷歌原生组件的Builder的模式,通过分层将逻辑简化,代码简洁。

Builder模式 实践

根据以上,我使用Builder模式重构了之前的小项目BottomPopUpDialog

BottomPopUpDialog


public class BottomPopUpDialog extends DialogFragment {


    private TextView mCancel;

    private LinearLayout mContentLayout;

    private Builder mBuilder;

    private static BottomPopUpDialog getInstance(Builder builder) {
        BottomPopUpDialog dialog = new BottomPopUpDialog();
        dialog.mBuilder = builder;
        return dialog;

    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //该方法需要放在onViewCreated比较合适, 若在 onStart 在部分机型(如:小米3)会出现闪烁的情况
        getDialog().getWindow().setBackgroundDrawableResource(mBuilder.mBackgroundShadowColor);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Holo_Light_NoActionBar);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.bottom_pop_up_dialog, null);
        initView(view);
        registerListener(view);
        setCancelable(true);
        return view;
    }


    private void initView(View view) {
        mContentLayout = (LinearLayout) view.findViewById(R.id.pop_dialog_content_layout);
        mCancel = (TextView) view.findViewById(R.id.cancel);
        initItemView();
    }


    private void registerListener(View view) {

        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    dismiss();
                }
                return false;
            }
        });

        mCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });
    }


    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            super.show(manager, tag);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void initItemView() {
        //循环添加item
        for (int i = 0; i < mBuilder.mDataArray.length; i++) {
            final PopupDialogItem dialogItem = new PopupDialogItem(getContext());
            dialogItem.refreshData(mBuilder.mDataArray[i]);

            //最后一项隐藏分割线
            if (i == mBuilder.mDataArray.length - 1) {
                dialogItem.hideLine();
            }

            //设置字体颜色
            if (mBuilder.mColorArray.size() != 0 && mBuilder.mColorArray.get(i) != 0) {
                dialogItem.setTextColor(mBuilder.mColorArray.get(i));
            }

            if (mBuilder.mLineColor != 0) {
                dialogItem.setLineColor(mBuilder.mLineColor);
            }

            mContentLayout.addView(dialogItem);

            dialogItem.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mBuilder.mListener.onDialogClick(dialogItem.getItemContent());
                    if (mBuilder.mIsCallBackDismiss) dismiss();
                }
            });
        }
    }



    public static class Builder {

        private String[] mDataArray;

        private SparseIntArray mColorArray = new SparseIntArray();

        private BottomPopDialogOnClickListener mListener;

        private int mLineColor;

        private boolean mIsCallBackDismiss = false;

        private int mBackgroundShadowColor = R.color.transparent_70;


        /**
         * 设置item数据
         */
        public Builder setDialogData(String[] dataArray) {
            mDataArray = dataArray;
            return this;
        }

        /**
         * 设置监听item监听器
         */
        public Builder setItemOnListener(BottomPopDialogOnClickListener listener) {
            mListener = listener;
            return this;
        }


        /**
         * 设置字体颜色
         *
         * @param index item的索引
         * @param color res color
         */
        public Builder setItemTextColor(int index, int color) {
            mColorArray.put(index, color);
            return this;
        }

        /**
         * 设置item分隔线颜色
         */
        public Builder setItemLineColor(int color) {
            mLineColor = color;
            return this;
        }

        /**
         * 设置是否点击回调取消dialog
         */
        public Builder setCallBackDismiss(boolean dismiss) {
            mIsCallBackDismiss = dismiss;
            return this;
        }


        /**
         * 设置dialog背景阴影颜色
         */
        public Builder setBackgroundShadowColor(int color) {
            mBackgroundShadowColor = color;
            return this;
        }


        public BottomPopUpDialog create() {
            return BottomPopUpDialog.getInstance(this);
        }


        public BottomPopUpDialog show(FragmentManager manager, String tag) {
            BottomPopUpDialog dialog = create();
            dialog.show(manager, tag);
            return dialog;
        }


    }


    public interface BottomPopDialogOnClickListener {
        /**
         * item点击事件回调
         *
         * @param tag item字符串 用于识别item
         */
        void onDialogClick(String tag);
    }

}



这是我结合前面的Builder模式写的BottomPopUpDialogBuildr模式是将一个对象的构建与显示分离,将不同的参数一个一个添加进去,也是对于外部隐藏实现细节,更是降低了耦合度,方便以后的自由扩展。还有,我的实现省去了Controller层代码,我把控制层和UI层放在一起,这样实现是为了简单,我觉得Controller层是在可以复用的场景下,使用起来更有价值,而小组件可以更简单的使用Builder模式。编程是简单实用

重构之后的调用


new BottomPopUpDialog.Builder()
      .setDialogData(getResources().getStringArray(R.array.popup_array))
      .setItemTextColor(2, R.color.colorAccent)
      .setItemTextColor(4, R.color.colorAccent)
      .setCallBackDismiss(true)
      .setItemLineColor(R.color.line_color)
      .setItemOnListener(new BottomPopUpDialog.BottomPopDialogOnClickListener() {
                                @Override
                                public void onDialogClick(String tag) {
                                    Snackbar.make(view, tag, Snackbar.LENGTH_LONG)
                                            .setAction("Action", null).show();
                                }
                            })
      .show(getSupportFragmentManager(), "tag");

最后

这是一个简单的编程模式,记录一下思路。