目前我们使用listview展示数据时,adapter的getView方法通常使用convertView.setTag(viewHolder)的方式来避免出现卡顿的情况,这种方式能使convertView得以复用,避免重复的调用inflate方法渲染界面。但是,如果使用不当,可能会出现数据错乱、重复的问题,比如下面这个demo:
这个demo是要listview在偶数行只显示大写字母,在奇数行既显示大写字母又显示小写字母:
第一屏的显示是没问题的,但是向下滑动几行,就会出现有问题了:
J这一行按理说应该只显示大写字母,但是连小写字母也显示出来了,而且显示的居然还是a。
demo的getView代码:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MyBean myBean = getItem(position);
MyHolder holder;
if(convertView == null){
holder = new MyHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.item_listview, null);
holder.tv_title = (TextView) convertView.findViewById(R.id.tv_title);
holder.tv_subtitle = (TextView) convertView.findViewById(R.id.tv_subtitle);
convertView.setTag(holder);
}else{
holder = (MyHolder) convertView.getTag();
}
holder.tv_title.setText(myBean.title);
//注意这里,布局文件tv_subtitle的visibility是GONE
if(position % 2 == 0){
holder.tv_subtitle.setVisibility(View.VISIBLE);
holder.tv_subtitle.setText(myBean.subTitle);
}
return convertView;
}
原因分析
通过setTag避免重复渲染,第一屏界面肯定没有问题,因为没有可复用的,但继续向下滑动时,就会出现复用了。下面这张图很好的说明了复用的情形:
假设一屏只能显示7个item,当item滑动到item8时,adapter的getView方法就不会再去调用inflate方法渲染页面,而是直接复用item1的页面。
需要注意的是,在滑动时,只有当最上面的item完全消失后,下面刚出来的item才会复用它的convertView,如果二者能同时出现,比如当最上面的item消失了50%,最下面的item露出了10%,那么最下面的item复用的不是最上面的item,而是最上面的item再往上的那个item。(有点拗口,但我觉得说的很明白了,上面那张图只供理解参考)
结合上面demo的getView代码分析:
布局文件tv_subtitle的visibility是GONE,只有满足下面的条件才会显示出来:
if(position % 2 == 0){
holder.tv_subtitle.setVisibility(View.VISIBLE);
holder.tv_subtitle.setText(myBean.subTitle);
}
显示J 的这行position % 2是1,所以不会走到if里面,而这行复用了A的那行,它的convertView是A那行的convertView,而A的tv_subtitle的visibility是VISIBLE,所以J这行也就显示出了tv_subtitle。有人可能问了,第一屏截止到H,为什么I没有复用A,而是J复用了?这个上面已经解释过了,I出来的时候A那行还没完全消失,所以不会复用A。
解决方案
就上面的demo来说,就是加一个else:
if(position % 2 == 0){
holder.tv_subtitle.setVisibility(View.VISIBLE);
holder.tv_subtitle.setText(myBean.subTitle);
}else{
holder.tv_subtitle.setVisibility(View.GONE);
}
因为convertView会复用,所以对ViewHolder里的每个组件,我们都要更新它的状态,如果不更新就可能会显示上一屏的数据。