写在前面的话 鉴于之前写了一篇动态表单是很早之前使用的东西,导致会误导很多android的学习的人,并且下载demo还因为当时资源必须设置积分的原因设置了积分,所以将之前的资源免费,并且本次demo资源也不会需要积分,各位放心食用。
动态表单其实对于android原生的来说,动态表单并不友好,我们一般都是借助 RecycleView(ListView) 来实现,然后在界面绘制的时候想办法解决这个问题,所以今天我们也会使用这种方式,然后这儿就使用了第三方BaseRecyclerViewAdapterHelper ,不多说直接开始吧。
思路: 要对列表的item做处理,我们还是需要在Adapter方面着手,由于我们的动态表单用的每一个item都会是不一样的,所以我们需要用到分组布局; 准备: 我们首先考虑的是表单对象类‘即adapter需要传入的对象类’,我们在构建表单对象的时候我们需要考虑分组布局的参数值,我们还需要item需要的标题和值,以及多选的可选值等数据,所以我们构建的对象的数据需要如下:
public final static int SWITCH = 0;
public final static int SWITCHMORE = 3;
public final static int EDIT = 2;
public final static int TEXT = 1;
public final static int TITLE = 4;
public final static int TIME = 5;
private boolean mast = false;
private int type;
private int id;
private String name;
private String value;
private int editExp;
private List values;
其中editExp适用于输入框的类型控制,values字段适用于单选多选的时候传入的参数对象,具体如下:
private String id;// 选项ID
private String value;// 选项值
private String other;// 选项其他参数 拓展可用,这儿可以直接换为泛型哦,这样就可以把我们需要的参数也传入进来
对象类准备好了,接下来就是布局,布局代码我就不贴了,都是比较简单的布局代码:准备常用的输入框、单选多选等;
adapter 这个是一个比较重要的类,我们基本上的逻辑都需要在此类实现,此类继承自BaseMultiItemQuickAdapter ,这个父类是可以自定义多布局,是非常实用的,具体的逻辑判断对象类传入进来的需要加载的布局类型 ,此处我们截取其中一段代码来分析:// 首先取出我们绘制的item的类型和布局
switch (helper.getItemViewType()) {
case FormBean.SWITCH:
// 判断如果是单选 两项(是否)
// 此处只是设置了是否必填,所以我加了*号
DrawableUtils.setDrawable(mContext, helper.getView(R.id.czyw_from_item_select_title), item.isMast()? R.mipmap.icon_mast_r : R.mipmap.icon_mast_w);
helper.setText(R.id.czyw_from_item_select_title, item.getName());
// 拿到选择按钮
Switch s = helper.getView(R.id.czyw_from_item_select_switch);
// 设置按钮打开和关闭情况下对应的值
s.setTextOn(item.getValues().get(0).getValue());
s.setTextOff(item.getValues().get(1).getValue());
// 判断是否有默认值,如果有则直接设置进入默认值
if (!StringUtils.isTrimEmpty(item.getValue())) {
// 默认赋值
for (int i = 0; i {
// 赋值
item.setValue(isChecked ? item.getValues().get(0).getId() : item.getValues().get(1).getId());
});
} else {
// 禁止点击
s.setEnabled(false);
if (ObjectUtils.isNotEmpty(item.getValue())) {
for (int i = 0; i < item.getValues().size(); i++) {
if (item.getValue().equals(item.getValues().get(i).getId())) {
s.setChecked(i == 0);
}
}
}
}
break;
}
按照上面分析的逻辑处理其他类型即可:判断类型-布局赋值-默认设置;
使用 使用前需要准备数据,此处我贴出测试数据:// 清除表单数据 可返回调用此方法重新构建
formBeans.clear();
// 选项值 单选
values1 = new ArrayList();
values1.add(new FormBean.FromValue("男(值)", "男"));
values1.add(new FormBean.FromValue("女(值)", "女"));
values1.add(new FormBean.FromValue("中性(值)", "中性"));
values1.add(new FormBean.FromValue("未知(值)", "未知"));
formBeans.add(new FormBean(FormBean.SWITCHMORE, 1, "性别", "未知(值)", values1));
values1 = new ArrayList();
values1.add(new FormBean.FromValue("0~20(值)", "0~20"));
values1.add(new FormBean.FromValue("20~40(值)", "20~40"));
values1.add(new FormBean.FromValue("40以上(值)", "40以上"));
formBeans.add(new FormBean(FormBean.SWITCHMORE, 2, "年龄段", "0~20(值)", values1));
// 输入框 字符串 整数 小数
formBeans.add(new FormBean(FormBean.EDIT, 3, "情况描述", ""));
formBeans.add(new FormBean(FormBean.EDIT, 4, "身高(cm)", "", 1));
formBeans.add(new FormBean(FormBean.EDIT, 5, "体重(kg)", "", 2));
values1 = new ArrayList();
values1.add(new FormBean.FromValue("是(值)", "是"));
values1.add(new FormBean.FromValue("否(值)", "否"));
formBeans.add(new FormBean(FormBean.SWITCH, 6, "是否已婚", "否(值)", values1));
values1 = new ArrayList();
values1.add(new FormBean.FromValue("是(值)", "是"));
values1.add(new FormBean.FromValue("否(值)", "否"));
formBeans.add(new FormBean(FormBean.SWITCH, 7, "是否去过武汉", "否(值)", values1));
// 多选
values1 = new ArrayList();
values1.add(new FormBean.FromValue("发烧(值)", "发烧"));
values1.add(new FormBean.FromValue("咳嗽(值)", "咳嗽"));
values1.add(new FormBean.FromValue("头晕(值)", "头晕"));
values1.add(new FormBean.FromValue("头痛(值)", "头痛"));
values1.add(new FormBean.FromValue("鼻塞(值)", "鼻塞"));
values1.add(new FormBean.FromValue("全身乏力(值)", "全身乏力"));
values1.add(new FormBean.FromValue("胸闷气短(值)", "胸闷气短"));
values1.add(new FormBean.FromValue("呼吸困难(值)", "呼吸困难"));
values1.add(new FormBean.FromValue("味觉失灵(值)", "味觉失灵"));
values1.add(new FormBean.FromValue("嗅觉失灵(值)", "嗅觉失灵"));
values1.add(new FormBean.FromValue("其他见备注(值)", "其他见备注"));
formBeans.add(new FormBean(FormBean.TEXT, 8, "其他情况", "", values1));
formBeans.add(new FormBean(FormBean.EDIT, 9, "备注", ""));
其中formBeans 就是adapter需要的对象,values1 就是多选单选的选项等。
然后我们只需要按照正常的adapter和recycleview来处理即可:
// 动态表单
rv.setLayoutManager(new LinearLayoutManager(this));
formAdapter = new FormAdapter(formBeans, true);
formAdapter.bindToRecyclerView(rv);
数据处理:我们只需要获取到adapter中的数据,对数据做遍历即可取出我们想要的数据:
// 获取数据
for (FormBean formBean : formAdapter.getData()) {
switch (formBean.getId()) {
case 4:
// ID为4的数据 此处单独判断ID可用来判断是否是必传字段|电话号码验证 等逻辑操作
// 例身高为必填 身高的ID我们设置了是4 就判断此处的Value是否为空,为空则提示 此为必填等
// 也可将对应的字段填充到对象中
break;
}
data.append(formBean.getName()).append("--->").append(formBean.getValue()).append("\n");
}
监听 监听其实也比较好处理,我们监听在adapter中处理即可,并且处理值等:
// 获取到view添加监听
view.setOnClickListener(v -> {
List beans = new ArrayList();
for (FormBean.FromValue value : item.getValues()) {
beans.add(new GridSelectBean(value.getValue(), value.getId()));
}
// 此处使用了dialogplus来处理选择布局
new ConfirmListDialog(mContext)
.setTitleStr("请选择")
.setList(beans)
.setConfirmListDialogListener(new ConfirmListDialogListener() {
@Override
public void onCheckSelect(List mList) {
}
@Override
public void onCheckSingle(GridSelectBean bean, int position) {
}
@Override
public void onItemClickListener(GridSelectBean bean, int position) {
// 赋值
item.setValue(bean.getValue());
view.setText(bean.getTitle());
}
})
.show();
});
其他说明
至此动态的加载表单算是结束了,布局可能有点丑陋,但是按照这个逻辑可以自己修改布局,所以问题不大。
最终效果图如下:
代码地址
本项目除了android动态表单,通过adapter来实现,还包含了dialogplus的再次封装,让使用更便捷,还包含有控件实现可拖拽的监听事件。
最后结语:前人栽树后人乘凉,我只是那个在你们乘凉的时候递给你们板凳和瓜子的人,让你们在乘凉的时候能够更舒适和方便一些,所以最终还是要感谢各位大佬们的开源的库和项目能得以引用和借鉴
推荐本次使用的第三方的库:
1. BaseRecyclerViewAdapterHelper
2. dialogplus
3. AndroidUtilCode
4. baseAdapter
作者:liu_yifang