美文网首页Android深入
设计封装通用筛选框

设计封装通用筛选框

作者: 旺仔_100 | 来源:发表于2021-04-27 20:00 被阅读0次

    一、功能要求介绍
    需要封装一个全局通用的弹窗。内部支持已知类型的view添加,对于未知类型view需要做扩展支持。筛选界面有返回按钮,重置按钮,确认按钮,和对应各种需要添加的view例如textview,组合型的editview,组合型的选择view,组合型的时间选择,组合型的radiogroup和radiobutton选择等,自定义的view等。自定义控件内部会处理所有的逻辑。简单来说就是用户只需要addview,在监听一下,就可以拿到对应筛选的结果,所有的处理均在内部进行。

    二、如何设计,使用了哪些设计模式,有哪些好处?


    整体的设计分包.png

    1.先看下inter包,里面放了几个接口:

    public interface IFilter {
        void reset();
        void cancel();
        FilterBaseResultBean getResultBean();
    }
    

    上面的接口定义了筛序弹窗的几个功能,所有需要添加的view,包括内部支持的view或者外部自定义的view都需要实现IFilter 。

    • reset 定义了筛选重置接口
    • cancel 定义了取消接口
    • getResultBean定义了确定需要获取数据的接口

    为什么这么设计,重置,取消,确定都是在popwindow中,为什么要设计到view中。这样是为了解耦合popwindow针对具体的view去特定的逻辑处理。
    popwindow中只需要遍历所有view,并调用IFilter 的reset,cancel和getResult。具体实现都放到具体的view中去做。后续添加内部支持的view不需要修改popwindow,这个遵从了单一职责,接口隔离,开闭职责,迪米特法则。

    public interface ICreateView {
        View createView(@ViewTypeAnnotation.ViewType String type,
                        Object object);
    }
    
    class CreateViewImpl implements ICreateView {
       private Context mContext;
    
        public CreateViewImpl(Context context) {
            this.mContext = context;
        }
    
        @Override
        public View createView(String type, Object object) {
            if (type.startsWith(ViewTypeAnnotation.TEXTVIEW) && object instanceof String) {
                return  addTextView((String) object);
            } else if (type.startsWith(ViewTypeAnnotation.EDITTEXT) && object instanceof FilterInputBean) {
                return  addInputView((FilterInputBean) object);
            } else if (type.startsWith(ViewTypeAnnotation.TIME) && object instanceof FilterTimeBean) {
                return addTimeView((FilterTimeBean) object);
            } else if (type.startsWith(ViewTypeAnnotation.RADIOGROUP) && object instanceof FilterRadioBean) {
                return addRadioButtons((FilterRadioBean) object);
            } else if (type.startsWith(ViewTypeAnnotation.SELECTLINEARLAYOUT) && object instanceof FilterSelectBean) {
                return  addSelectView((FilterSelectBean) object);
            } else if (type.startsWith(ViewTypeAnnotation.CUSTOM_VIEW) && object != null && object instanceof IFilter){
                return (View) object;
            }
            else {
                throw new RuntimeException(type + " 是不支持的类型,或者类型与传入参数不一致," +
                        "请修改成ViewTypeAnnotation.ViewType 支持的类型 和 对应的参数");
            }
        }
    
        private TextView addTextView(String o) {
            TextView view = new TextView(mContext);
            view.setText(o);
            return view;
        }
    
        private CommonInputEditText addInputView(FilterInputBean bean) {
            CommonInputEditText commonInputEditText = new CommonInputEditText(mContext);
            commonInputEditText.setLeftText(bean.getLeftText());
            commonInputEditText.setRightHint(bean.getRightHint());
            commonInputEditText.setInputType(bean.getInputType());
            commonInputEditText.setMustFillInVisible(bean.isMustFillIn());
            return commonInputEditText;
        }
    
    
        private CommonFilterTime addTimeView(FilterTimeBean bean) {
            CommonFilterTime commonFilterTime = new CommonFilterTime(mContext);
            commonFilterTime.setTextHint(bean.getTimeHint());
            commonFilterTime.setMustFillInVGone(bean.isMustFillIn());
            return commonFilterTime;
        }
    
        private CommonRadioGroup addRadioButtons(FilterRadioBean bean) {
            List list = bean.getList();
            if (list == null) {
                throw new RuntimeException("FilterRadioBean 不能为 null");
            }
            String title = bean.getTitle();
            if (!TextUtils.isEmpty(title)) {
                addTextView(title);
            }
            CommonRadioGroup radioGroup = new CommonRadioGroup<>(mContext, list,bean.getLayout());
            return radioGroup;
        }
    
        private CommonSelectLinearLayout addSelectView(FilterSelectBean bean) {
            if(bean == null){
                throw new RuntimeException("FilterSelectBean 不能为 null");
            }
            List list = bean.getList();
            if(list == null){
                throw new RuntimeException("FilterSelectBean#getList() list不能为 null");
            }
            CommonSelectLinearLayout commonSelectLinearLayout = new CommonSelectLinearLayout(mContext, list);
            commonSelectLinearLayout.setLeftText(bean.getLeftText());
            commonSelectLinearLayout.setRightText(bean.getCenterHint());
            commonSelectLinearLayout.setMustFillInVisible(bean.isMustFillIn());
            return commonSelectLinearLayout;
        }
    
    }
    

    把addview的逻辑使用接口抽离。popwindow里面通过ICreateView 的实现类CreateViewImpl来根据不同类型去添加不同的view。

    public class ViewTypeAnnotation {
        public static final String TIME = "time";
        public static final String TEXTVIEW = "textView";
        public static final String EDITTEXT = "editText";
        public static final String RADIOGROUP = "Radiogroup";
        public static final String SELECTLINEARLAYOUT = "SelectLinearLayout";
        public static final String CUSTOM_VIEW = "custom_view";
    
    
        @StringDef({TIME,TEXTVIEW,EDITTEXT,RADIOGROUP,SELECTLINEARLAYOUT,CUSTOM_VIEW})
        @Retention(RetentionPolicy.SOURCE)
        @Target(ElementType.PARAMETER)
        public @interface ViewType{
    
        }
    
    }
    
    

    上图,使用注解,一方面可以约束传参,另一方面可以可以代替枚举,根据类型来create不同的view。

    public interface OnResultLisenter {
        void onResult(List<FilterBaseResultBean> resultList);
    }
    

    提供一个获取筛选结果的接口

    public class CommonFilterPop extends PopupWindow {
        private Activity mContext;
        private TextView mTvCancel;
        private View mTvReset;
        private View mTvConfirm;
        private LinearLayout mLlContainer;
        private LinkedHashMap<String,Object> mViewMap;
        private LayoutInflater mLayoutInflater;
        private OnResultLisenter mOnResultLisenter;
        private List<FilterBaseResultBean> mResultList = new ArrayList<>();
        private List<IFilter> mViewLists;
        private TextView mTvTitle;
        private String mTitle;
    
    
        private CommonFilterPop(Builder builder) {
            super(builder.context);
            this.mContext = builder.context;
            this.mTitle = builder.mTitle;
            this.mViewMap = builder.mViewMap;
            mViewLists = new ArrayList<>();
            this.mOnResultLisenter = builder.mOnResultLisenter;
            init();
        }
    
        public static final class Builder {
            Activity context;
            //负责构造所有的
            LinkedHashMap<String,Object> mViewMap = new LinkedHashMap<>();
            OnResultLisenter mOnResultLisenter;
            String mTitle;
    
            public Builder(Activity context) {
                this.context = context;
            }
    
            public Builder setTitle(String title) {
                mTitle = title;
                return this;
            }
    
            public Builder addView(@ViewTypeAnnotation.ViewType String type,Object object){
                mViewMap.put(type.concat(String.valueOf(object.hashCode())),object);
                return this;
            }
    
            public Builder setOnResultListener(OnResultLisenter onResultLisenter) {
                mOnResultLisenter = onResultLisenter;
                return this;
            }
    
    
            public CommonFilterPop build() {
                return new CommonFilterPop(this);
            }
    
    
        }
    
        private void init() {
            mLayoutInflater = LayoutInflater.from(mContext);
            initPop();
    
        }
    
        private void initPop() {
            View view = LayoutInflater.from(mContext).inflate(R.layout.fp_phone_popup_common_fliter, null);
            findView(view);
            mTvTitle.setText(mTitle);
            //开始添加控件
            addChildViews();
            setContentView(view);
            setWidth(LinearLayout.LayoutParams.MATCH_PARENT);
            setHeight(LinearLayout.LayoutParams.MATCH_PARENT);
            setBackgroundDrawable(new ColorDrawable(0));
            setAnimationStyle(R.style.fp_phone_popupAnimation);
            update();
            setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
            setTouchable(true); // 设置popupwindow可点击
            setOutsideTouchable(true); // 设置popupwindow外部可点击
            setFocusable(true); // 获取焦点
            initListener();
        }
    
        /**
         * 根据配置添加view
         *
         */
    
        private void addChildViews() {
            if (mViewLists != null) {
                ICreateView createView = new CreateViewImpl(mContext);
    
                if (mViewMap == null){
                    throw new RuntimeException("需要addview 来完善筛选弹窗");
                }
               for (Map.Entry<String,Object> entry : mViewMap.entrySet()){
                   View view = createView.createView(entry.getKey(),entry.getValue());
                   mLlContainer.addView(view);
                   //有些控件不需要返回值,可以不用实现IFilter
                   if(view instanceof IFilter){
                       mViewLists.add((IFilter) view);
                   }
                   //添加一个line,最后一个就不用添加了
                   View line = mLayoutInflater.inflate(R.layout.fp_phone_common_line, mLlContainer, false);
                   mLlContainer.addView(line);
               }
            }
        }
    
    
        private void initListener() {
    
            RxViewClicksUtil.click(mTvConfirm, new Consumer() {
                @Override
                public void accept(Object o) throws Exception {
                    //确定   需要get到每一个控件的id之类的(用于传递给服务器)也需要get到每一个控件的结果,后面到页面做一些判断或者其他逻辑
                    //所以我们需要控件封装一个返回结果的类把对应需要的东西返回  用一个集合把所有的结果返回给使用者
                    if (mOnResultLisenter != null) {
                        mResultList.clear();
                        for (int i = 0; i < mViewLists.size(); i++) {
                            FilterBaseResultBean resultBean = mViewLists.get(i).getResultBean();
                            if (resultBean != null) {
                                resultBean.setIndex(i);
                                mResultList.add(resultBean);
                            }
                        }
                        mOnResultLisenter.onResult(mResultList);
                        dismiss();
                    }
                }
            });
            RxViewClicksUtil.click(mTvReset, new Consumer() {
                @Override
                public void accept(Object o) throws Exception {
                    //重置
                    // 调用每一个控件的重置方法
                    //  遍历每一个view并调用
                    for (IFilter mViewList : mViewLists) {
                        mViewList.reset();
                    }
                }
            });
    
            RxViewClicksUtil.click(mTvCancel, o -> {
                for (IFilter mViewList : mViewLists) {
                    mViewList.cancel();
                }
                dismiss();
            });
        }
    
    
        private void findView(View view) {
            mTvTitle = view.findViewById(R.id.fp_phone_tv_title);
            mTvCancel = view.findViewById(R.id.fp_phone_tv_cancel);
            mTvReset = view.findViewById(R.id.fp_phone_tv_reset);
            mTvConfirm = view.findViewById(R.id.fp_phone_tv_confirm);
            mLlContainer = view.findViewById(R.id.fp_phone_ll_container);
        }
    
    }
    
    

    上图的popwindow只是做一些界面相关的通用操作,把addview的view交个接口去做,把获取结果,重置,取消等操作通过接口下方到自定义控件中实现。减小了popwindow和自定义添加控件的耦合。扩展性好。

    相关文章

      网友评论

        本文标题:设计封装通用筛选框

        本文链接:https://www.haomeiwen.com/subject/krxprltx.html