社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
太白(小白太白)我太久没写博客了,今天手痒写一篇。前两天项目上用到了动态表单,哪种动态表单呢,就是表单结构要从服务器端抓取(后台会随时更改表单结构)。这可是难为死我了,网上找了不少,四处翻看不少文章,都没找到答案,没办法,只能自己做了。
动态表单目前最直接的体现应该就有一种:那就是树形结构,树形结构的最基础的东西就是listview,所以我想到动态表单也可以从这个listview着手。从listview着手又该怎么做呢,当然是操作item啦。动态表单不外乎就是展示不同的item,所以我们需要写不同的item给listview。比如我们动态表单有复选框,有输入框,文本框等。这个时候我们就需要一个布局来装下这些动态添加的布局控件,布局可以相对布局,也可以线性布局。
思路是有了,那我们就要付诸实际了。首先我们需要分析数据结构,首先动态表单数据结构我们需要一个辨别控件类型的标识,还需要控件的前缀或者文本、控件的id、控件的默认值或者控件的最终值(和后台同步的值)。这些个数据结构怎么来,当然是找你们服务端的好朋友要了,你要是自己能写也行,哈哈。
当然我的这个数据结构和他们这个有一点点区别,分为两层,大家的可以根据自己的结构来区别,大致如下(我这demo用了阿里巴巴的解析工具):
/**
* 动态表单数据结构
* @author lyf
*
*/
public class ModelDynamicViewTo {
@JSONField
public String preText;// 外层文本
@JSONField
public boolean hasFields;// 是否有内层
@JSONField
public List<ModelFile> fields;// 内层结构
}
内部结构:
/**
* 动态表单内层结构
* @author lyf
*
*/
public class ModelFile {
@JSONField
public String fieldType;// view类型 1.文本框;2.时间输入;3.复选框;5.文本域(备注等输入框)
@JSONField
public String fname;// view文本
@JSONField
public String id;// viewID
@JSONField
public String rowId;
@JSONField
public String value;// view值
// HH时mm分
// yyyy年MM月dd日HH时mm分
// yyyy年MM月dd日
@JSONField
public String pattern;// 当为时间输入框时时间类型(本demo分为上述时间类型)
}
第一种是复选框布局:
<TextView
android:layout_marginTop="4dp"
android:id="@+id/dynamic_view_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#10ABE4"
android:padding="3dp"
android:text="多选框名"
android:textColor="#ffffff"
android:textSize="16sp" />
<RelativeLayout
android:id="@+id/dynamic_view_item_rl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp" >
<LinearLayout
android:id="@+id/dynamic_view_item_multiple_ck"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="8dp"
android:orientation="horizontal"
android:paddingLeft="5dp"
android:paddingRight="5dp" >
</LinearLayout>
</RelativeLayout>
第二种布局,就是一个文本一个输入框咯(时间、文本输入等都可用),这儿就不贴了。
布局我们准备好了,就是代码了。代码就要根据我们的数据结构来了。在adapter里面的getView()方法里面判断咯(老板:你这个加载这么慢,这很影响用户体验 我:exm???你要动态表单布局要重新渲染,影响了性能怪我咯,哈哈)。根据我们之前的结构:
首先我们需要判断是否有子布局文件:没有的话我们当标题处理(这个就体现了这个双层结构的好处了),字体加粗加大啥的爱咋整咋整。
然后有子文件的情况下,需要加载我们的第一种布局了,接下来我们就要判断filetype的类型了。具体的判断内容我直接贴出来吧,我是一个懒人:
// 文本框
convertView = mInflater.inflate(
R.layout.dynamic_view_item_multiple_choice, null);
holder.tvNameMain = (TextView) convertView
.findViewById(R.id.dynamic_view_item_name);
holder.dynamic_view_item_multiple_ck = (LinearLayout) convertView
.findViewById(R.id.dynamic_view_item_multiple_ck);
if (StringUtils.isNotBlank(mdvs.get(position).preText)) {
holder.tvNameMain.setText(mdvs.get(position).preText);
} else {
holder.tvNameMain.setVisibility(View.GONE);
}
LinearLayout llTest = null;
if (StringUtils.isNotBlank(mdvs.get(position).fields)
&& mdvs.get(position).fields.size() > 0) {
for (int i = 0; i < mdvs.get(position).fields.size(); i++) {
if ("1".equals(mdvs.get(position).fields.get(i).fieldType)) {// 文本框
// holder.tvNameMain.setVisibility(View.GONE);
llTest = (LinearLayout) mInflater.inflate(
R.layout.dynamic_view_item_text, null);
holder.tvName = (TextView) llTest
.findViewById(R.id.dynamic_view_item_name);
holder.dynamic_view_item_text_tv = (TextView) llTest
.findViewById(R.id.dynamic_view_item_text_tv);
holder.dynamic_view_item_name_after = (TextView) llTest
.findViewById(R.id.dynamic_view_item_name_after);
if (StringUtils
.isNotBlank(mdvs.get(position).fields.get(i).preName)
&& !"".equals(mdvs.get(position).fields.get(i).preName)) {
holder.tvName
.setText(mdvs.get(position).fields.get(i).preName);
} else if (StringUtils
.isNotBlank(mdvs.get(position).preText)
&& !"".equals(mdvs.get(position).preText.trim())) {
holder.tvName.setText(mdvs.get(position).preText);
} else {
holder.tvName.setVisibility(View.GONE);
}
if (StringUtils
.isNotBlank(mdvs.get(position).fields.get(i).afterName)
&& !"".equals(mdvs.get(position).fields.get(i).afterName
.trim())) {
holder.dynamic_view_item_name_after.setText(mdvs
.get(position).fields.get(i).afterName);
holder.dynamic_view_item_name_after
.setVisibility(View.VISIBLE);
}
if (StringUtils
.isNotBlank(mdvs.get(position).fields.get(i).valueT)) {
holder.dynamic_view_item_text_tv.setText(mdvs
.get(position).fields.get(i).valueT);
} else {
String s = mdvs.get(position).fields.get(i).preName;
holder.dynamic_view_item_text_tv.setHint(s);
}
holder.dynamic_view_item_text_tv.setGravity(Gravity.RIGHT);
holder.dynamic_view_item_text_tv
.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (StringUtils.isNotBlank(((TextView) v)
.getHint().toString())
|| StringUtils
.isNotBlank(((TextView) v)
.getText()
.toString())) {
for (int j = 0; j < mdvs.get(position).fields
.size(); j++) {
String s = mdvs.get(position).fields
.get(j).preName;
if (((TextView) v)
.getText()
.toString()
.equals(mdvs.get(position).fields
.get(j).valueT)) {
location = j;
} else if (((TextView) v).getHint()
.toString().equals(s)) {
location = j;
} else {
location = 0;
}
}
} else {
location = 0;
}
showEditDialog(mdvs.get(position).fields
.get(location), position, v, true);
}
});
} else if ("2"
.equals(mdvs.get(position).fields.get(i).fieldType)) {// 时间选择
llTest = (LinearLayout) mInflater.inflate(
R.layout.dynamic_view_item_text, null);
holder.tvName = (TextView) llTest
.findViewById(R.id.dynamic_view_item_name);
holder.dynamic_view_item_text_tv = (TextView) llTest
.findViewById(R.id.dynamic_view_item_text_tv);
if (StringUtils
.isNotBlank(mdvs.get(position).fields.get(i).preName)
&& !"".equals(mdvs.get(position).fields.get(i).preName)) {
holder.tvName
.setText(mdvs.get(position).fields.get(i).preName);
} else if (StringUtils
.isNotBlank(mdvs.get(position).preText)
&& !"".equals(mdvs.get(position).preText.trim())) {
holder.tvName.setText(mdvs.get(position).preText);
} else {
holder.tvName.setVisibility(View.GONE);
}
if (StringUtils
.isNotBlank(mdvs.get(position).fields.get(i).valueT)) {
holder.dynamic_view_item_text_tv.setText(mdvs
.get(position).fields.get(i).valueT);
} else {
String s = mdvs.get(position).fields.get(i).preName;
holder.dynamic_view_item_text_tv.setHint(s);
}
holder.dynamic_view_item_text_tv.setGravity(Gravity.RIGHT);
holder.dynamic_view_item_text_tv
.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (StringUtils.isNotBlank(((TextView) v)
.getHint().toString())
|| StringUtils
.isNotBlank(((TextView) v)
.getText()
.toString())) {
for (int j = 0; j < mdvs.get(position).fields
.size(); j++) {
if (((TextView) v)
.getText()
.toString()
.equals(mdvs.get(position).fields
.get(j).preName)) {
location = j;
} else if (((TextView) v)
.getHint()
.toString()
.equals(mdvs.get(position).fields
.get(j).preName)) {
location = j;
} else {
location = 0;
}
}
} else {
location = 0;
}
setTime(v, mdvs.get(position).fields
.get(location));
}
});
} else if ("5"
.equals(mdvs.get(position).fields.get(i).fieldType)) {// 文本域
holder.tvNameMain.setVisibility(View.VISIBLE);
if (StringUtils
.isNotBlank(mdvs.get(position).fields.get(i).preName)
&& !"".equals(mdvs.get(position).fields.get(i).preName)) {
holder.tvNameMain.setText(mdvs.get(position).fields
.get(i).preName);
} else if (StringUtils
.isNotBlank(mdvs.get(position).preText)
&& !"".equals(mdvs.get(position).preText.trim())) {
holder.tvNameMain.setText(mdvs.get(position).preText);
} else {
holder.tvNameMain.setVisibility(View.GONE);
}
llTest = (LinearLayout) mInflater.inflate(
R.layout.dynamic_view_item_text, null);
holder.tvName = (TextView) llTest
.findViewById(R.id.dynamic_view_item_name);
holder.dynamic_view_item_text_tv = (TextView) llTest
.findViewById(R.id.dynamic_view_item_text_tv);
holder.dynamic_view_item_text_tv.setMinHeight(100);
holder.tvName.setVisibility(View.GONE);
if (StringUtils
.isNotBlank(mdvs.get(position).fields.get(i).valueT)) {
holder.dynamic_view_item_text_tv.setText(mdvs
.get(position).fields.get(i).valueT);
} else {
String s = mdvs.get(position).fields.get(i).preName;
holder.dynamic_view_item_text_tv.setHint(s);
}
holder.dynamic_view_item_text_tv
.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (StringUtils.isNotBlank(((TextView) v)
.getHint().toString())
|| StringUtils
.isNotBlank(((TextView) v)
.getText()
.toString())) {
for (int j = 0; j < mdvs.get(position).fields
.size(); j++) {
if (((TextView) v)
.getText()
.toString()
.equals(mdvs.get(position).fields
.get(j).preName)) {
location = j;
} else if (((TextView) v)
.getHint()
.toString()
.equals(mdvs.get(position).fields
.get(j).preName)) {
location = j;
} else {
location = 0;
}
}
} else {
location = 0;
}
showEditDialog(mdvs.get(position).fields
.get(location), position, v, true);
}
});
} else if ("3"
.equals(mdvs.get(position).fields.get(i).fieldType)
|| "4".equals(mdvs.get(position).fields.get(i).fieldType)) {// 复选框
if (StringUtils.isNotBlank(mdvs.get(position).preText)
&& !"".equals(mdvs.get(position).preText)) {
holder.tvNameMain.setVisibility(View.VISIBLE);
} else {
holder.tvNameMain.setVisibility(View.GONE);
}
llTest = (LinearLayout) mInflater.inflate(
R.layout.dynamic_view_item_multiple_choice, null);
holder.tvName = (TextView) llTest
.findViewById(R.id.dynamic_view_item_name);
holder.tvName.setVisibility(View.GONE);
holder.dynamic_view_item_multiple_ck = (LinearLayout) llTest
.findViewById(R.id.dynamic_view_item_multiple_ck);
holder.tvName
.setText(mdvs.get(position).fields.get(i).preName);
if (mdvs.get(position).fields.get(i).preName != null
&& !mdvs.get(position).fields.get(i).preName
.equals("")) {
// 动态添加复选框
CheckBox ch = new CheckBox(contextT);
ch.setText(mdvs.get(position).fields.get(i).preName);
holder.dynamic_view_item_multiple_ck.addView(ch);
ch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(
CompoundButton buttonView, boolean isChecked) {
if (StringUtils.isNotBlank(buttonView.getText()
.toString())) {
for (int j = 0; j < mdvs.get(position).fields
.size(); j++) {
if (buttonView
.getText()
.toString()
.equals(mdvs.get(position).fields
.get(j).preName)) {
location = j;
}
}
}
// 复选框监听
if (isChecked) {
mdvs.get(position).fields.get(location).valueT = mdvs
.get(position).fields.get(location).value;
} else {
mdvs.get(position).fields.get(location).valueT = "";
}
}
});
// 设置默认值
if (mdvs.get(position).fields.get(i).valueT != null
&& !"".equals(mdvs.get(position).fields.get(i).valueT
.trim())) {
ch.setChecked(true);
}
}
}
if (llTest != null) {
((ViewGroup) convertView).addView(llTest);
break;
}
}
}
注意事项:
1.思路-判断view类型-动态添加view控件-找到view位置-对view监听(填充数据和读取数据-推荐直接用list来加载和监听)
2.当某些布局携带的文本为null时,为了美观我们需要隐藏显示文本的这个view控件
例如:
if (StringUtils.isNotBlank(mdvs.get(position).preText) && !"".equals(mdvs.get(position).preText)) {
holder.tvNameMain.setVisibility(View.VISIBLE);
} else {
holder.tvNameMain.setVisibility(View.GONE);
}
3.我们需要为每个view设置标签,内部监听的时候才不会出现输入数据后填充数据错误
例如:
添加标签
if (StringUtils.isNotBlank(mdvs.get(position).fields.get(i).valueT)) {
holder.dynamic_view_item_text_tv.setText(mdvs.get(position).fields.get(i).valueT);
} else {
String s = mdvs.get(position).fields.get(i).preName;
holder.dynamic_view_item_text_tv.setHint(s);
}
判断标签
@Override
public void onClick(View v) {
if (StringUtils.isNotBlank(((TextView) v).getHint().toString()) || StringUtils.isNotBlank(((TextView) v).getText().toString())) {
for (int j = 0; j < mdvs.get(position).fields.size(); j++) {
String s = mdvs.get(position).fields.get(j).preName;
if (((TextView) v).getText().toString().equals(mdvs.get(position).fields.get(j).valueT)) {
location = j;
} else if (((TextView) v).getHint().toString().equals(s)) {
location = j;
} else {
location = 0;
}
}
} else {
location = 0;
}
4.在循环结束时我们可以适当的给他一个break,这样就会减少循环次数适当的提高一点性能咯。
5.因为输入框在listview里面的特殊原因,所以我给他一个弹出框输入(想想移动端QQ空间评论别人的时候那个点击输入框的时候的弹出框吧,哈哈)。
最后贴几张效果图(布局啊颜色这些可以自己去定制化,都是不存在的):
1.整体布局
2.输入框
3.时间选择
最后,可能demo还是有很多bug等待大家去发掘。哈哈。里面的时间选择框也是大佬们写的,我只是搬运工。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!