Android深入

设计封装通用筛选框

2021-04-27  本文已影响0人  旺仔_100

一、功能要求介绍
需要封装一个全局通用的弹窗。内部支持已知类型的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 。

为什么这么设计,重置,取消,确定都是在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和自定义添加控件的耦合。扩展性好。

上一篇下一篇

猜你喜欢

热点阅读