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一次。

图示如下:

adapter-recycler

这不是今天的重点,重点是在具体开发中,发现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。