此文章已经不适用于现在的android了,为了对部分最近看到此文章而被耽误的小伙伴致歉,抽时间新写了一篇,不管是流畅度还是使用都比现在更好更方便,请移步~
太白(小白太白)我太久没写博客了,今天手痒写一篇。前两天项目上用到了动态表单,哪种动态表单呢,就是表单结构要从服务器端抓取(后台会随时更改表单结构)。这可是难为死我了,网上找了不少,四处翻看不少文章,都没找到答案,没办法,只能自己做了。
动态表单目前最直接的体现应该就有一种:那就是树形结构,树形结构的最基础的东西就是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分为上述时间类型)
}
接下来就是重点自定义的布局了。首先我们需要一个xml布局文件,一个普通的listview(简单代码就不贴了)。然后是item的布局(为了美观我用了两种布局方式,而不是动态的用了一种):
第一种是复选框布局:
<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>
如果你问我为什么LinearLayout要用RelativeLayout包裹,当然是因为这样我们的布局才能稍稍好看点,因为复选框在右边的话总比左边好看点,而要用LinearLayout的原因是因为我们不知道我们的复选框到底有多少多少选项,水平布局总会放不下,而且也有不同手机不同的适配问题,所以我这个demo就从简,直接垂直布局了。
第二种布局,就是一个文本一个输入框咯(时间、文本输入等都可用),这儿就不贴了。
布局我们准备好了,就是代码了。代码就要根据我们的数据结构来了。在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;
}
}
}
break;
}
}
}
代码太多你肯定没有心情去看完,所以我也给和我一样的人准备了下面一段话
注意事项:
1.思路-判断view类型-动态添加view控件-找到view位置-对view监听(填充数据和读取数据-推荐直接用list来加载和监听)
2.当某些布局携带的文本为null时,为了美观我们需要隐藏显示文本的这个view控件
例如:
<strong>if (StringUtils.isNotBlank(mdvs.get(position).preText) && !"".equals(mdvs.get(position).preText)) {
holder.tvNameMain.setVisibility(View.VISIBLE);
} else {
holder.tvNameMain.setVisibility(View.GONE);
}</strong>
3.我们需要为每个view设置标签,内部监听的时候才不会出现输入数据后填充数据错误
例如:
添加标签
<strong>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);
}</strong>
判断标签
<strong>@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;
}</strong>
上述我就简单的设置了标识而已,哈哈,也不算标签。为什么要赋值0,因为我只是想初始化而已。
4.在循环结束时我们可以适当的给他一个break,这样就会减少循环次数适当的提高一点性能咯。
5.因为输入框在listview里面的特殊原因,所以我给他一个弹出框输入(想想移动端QQ空间评论别人的时候那个点击输入框的时候的弹出框吧,哈哈)。
最后贴几张效果图(布局啊颜色这些可以自己去定制化,都是不存在的):
1.整体布局
2.输入框
3.时间选择
最后,可能demo还是有很多bug等待大家去发掘。哈哈。里面的时间选择框也是大佬们写的,我只是搬运工。