Android开发中,经常会用到ListView,当然,也会用到Adapter为ListView绑定数据。通常,我们采用继承BaseAdapter并实现getView等方法的方式为ListView渲染数据,ListView中每个元素都会去调用getView做渲染。但是,如果ListView要展示数据非常庞大,创建成百上千个View是不可取的,于是乎,Android为我们提供了一套Recycler机制,可以重复利用已创建的View,降低对象开销。
原理如下:
在一个完整的ListView第一次出现时,每个Item都是null的,getView的时候会跑到需要inflate一个Item的代码段,假设整个view只能最多显示10个item,那么当滑动到第11个Item的时候,第一个item会放入”recycler”,如果第11个Item和放入”Recycler”的item的view一致,那么就会使用”Recycler”里面的Item来显示,从而不用再重复inflate一次。
图示如下:
这不是今天的重点,重点是在具体开发中,发现getView被重复调用,按照网上说法,getView执行的次数和你的getCount没有直接的关系,getCount和你ListView里面的条目数量(行数量)有关系 ,getView方法执行次数取决于你屏幕上显示几个条目,比如你有100行 ,但是你一屏只能显示5行,那么启动程序的时候 系统调用5次getView方法,当你把listView往下拉的时候会显示出其他未显示的行,这样系统就会调用getView方法,每显示一个新的行就调用一次getView,所以你要是不停的上下滑动listVew那getView理论上是可以调用任意次数的。新手通常遇到的重复是在这5个范围内重复,此问题在于,ListView没有取到实际的高度,无法确定取多少View来填充ListView,也就是运行getView的具体运行次数,所以要尝试性的探测单个Item的高度,然后再去渲染,所以,我们需要做的是,为ListView设置高度为fill_parent(有文章表示,可以通过判断parent.getChildCount是否等于当前position来确定是否有效position,实际使用效果不佳),问题解决,但是在列表有输入框等情况下,激活输入,弹出键盘,有些输入法会出现把窗体压缩的情况,于是,Adapter的getView又会重新执行,这里就不可避免了。
上面已提及,getView多次调用实际上是不可避免的,但我们只需保证每次渲染索引对应数据,一切问题就迎刃而解,这就是我们的第二个问题,position错位,因为免不了在ListView中使用EditView等,而多变的需求又致使我们需要在getView里面为元素绑定事件,而getView又会重复调用,所以,我们如果不做判断直接在getView里面绑定事件,那么可想而知,操作几次后可能会有n个事件在同一个View上面,为了解决这样的问题,我们通常在判断View为空的情况inflate,同时绑定事件,这样看似完美,实则存在着潜在错误,如果是绑定click等事件,每次回调都会传入当前View,也就没什么问题,但如果是类似TextWatcher事件,回调不知道当前View,由于java语言特性,内部类传入值必须为final,这样我们在回调里面直接拿到的始终是最后一个初始化的View对应的position,很难与当前Item所对应数据进行交互等。
为了解决这个问题,我们通过实现TextWatcher接口,同时保存当前View所对应对象或者要操作的对象,代码如下:
class MyTextWatcher implements TextWatcher { private ViewHolder viewHold; public MyTextWatcher(ViewHolder viewHold) { this.viewHold = viewHold; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { //当前索引 Object position = viewHold.editText.getTag(); } }
getView内这样写:
@Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { LayoutInflater inflater = LayoutInflater.from(context); convertView = inflater.inflate(R.layout.submit_order_activity_item, null); viewHolder = new ViewHolder(); viewHolder.editText = (EditText) convertView.findViewById(R.id.edit_text); viewHolder.editText.addTextChangedListener(new MyTextWatcher(viewHold)); convertView.setTag(viewHold); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.editText.setTag(position); return convertView; }
其中,上面两个方法用到的ViewHolder也是为了保存中间变量创建的实体类,如下:
class ViewHolder { EditText editText; }
这里为了说明方法,简化了对应代码,实际使用中你可能需要保存其他信息等,可以在getView内多传入信息到ViewHolder。
上一篇: 微信小程序之我见 下一篇: iScroll5实现卡片左右滑动及动态加载