设计封装通用筛选框
一、功能要求介绍
需要封装一个全局通用的弹窗。内部支持已知类型的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和自定义添加控件的耦合。扩展性好。