首页 > 代码库 > Android中ListView滑动时数据混乱

Android中ListView滑动时数据混乱

相信做过Android应用开发的或多或少的都遇到过这样的问题,要不就是在ListView滑动时出现数据混乱,或者是GridView滑动时出现数据混乱。先来看看一位网友写的文章,个人感觉这篇文章挺不错的:


Android ListView滑动过程中图片显示重复错位闪烁问题解决

主要分析Android ListView滚动过程中图片显示重复、错乱、闪烁的原因及解决方法,顺带提及ListView的缓存机制。
1、原因分析
ListView item缓存机制
:为了使得性能更优,ListView会缓存行item(某行对应的View)。ListView通过adapter的getView函数获得每行的item。滑动过程中,

a. 如果某行item已经滑出屏幕,若该item不在缓存内,则put进缓存,否则更新缓存;
b. 获取滑入屏幕的行item之前会先判断缓存中是否有可用的item,如果有,做为convertView参数传递给adapter的getView。
更具体可见源代码ListView.obtainView。

 

这样,如下的getView写法就可以充分利用缓存大大提升ListView的性能。即便上万个行item,最多inflate的次数为n,n为一屏最多显示ListView 行item的个数。

ListView Adapter getView写法
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
        convertView = inflater.inflate(R.layout.list_item, null);
        holder = new ViewHolder();
        ……
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder)convertView.getTag();
    }
}
 
/**
* ViewHolder
*
* @author trinea@trinea.cn 2013-08-01
*/
private static class ViewHolder {
 
    ImageView appIcon;
    TextView  appName;
    TextView  appInfo;
}

这样提升了性能,但同时也会造成另外一些问题:

a. 行item图片显示重复
这个显示重复是指当前行item显示了之前某行item的图片。
比如ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中listView已经滑动到了第14行,且滑动过程中该图片加载结束,第2行已不在屏幕内,根据上面介绍的缓存原理,第2行的view可能被第14行复用,这样我们看到的就是第14行显示了本该属于第2行的图片,造成显示重复。

 

b. 行item图片显示错乱
这个显示错乱是指某行item显示了不属于该行item的图片。
比如ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中listView已经滑动到了第14行,第2行已不在屏幕内,根据上面介绍的缓存原理,第2行的view可能被第14行复用,第14行显示了第2行的View,这时之前的图片加载结束,就会显示在第14行,造成错乱。

 

c. 行item图片显示闪烁
上面b的情况,第14行图片又很快加载结束,所以我们看到第14行先显示了第2行的图片,立马又显示了自己的图片进行覆盖造成闪烁错乱。

 

2、解决方法
通过上面的分析我们知道了出现错乱的原因是异步加载及对象被复用造成的,如果每次getView能给对象一个标识,在异步加载完成时比较标识与当前行item的标识是否一致,一致则显示,否则不做处理即可。
下面以使用ImageCache为ListView提供图片获取缓存为例,ListView中强烈推荐使用ImageCache。
首先在listview adapter的getView中添加

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
    ViewHolderholder;
    if(convertView==null){
        convertView=inflater.inflate(R.layout.list_item,null);
        holder=newViewHolder();
        ……
        convertView.setTag(holder);
    }else{
        holder=(ViewHolder)convertView.getTag();
    }
 
    ……
    // add tag for image, to compare it when image loaded finish
    imageView.setTag(imageUrl);
    // if not in cache, restore default
    if(!Cache.ICON_CACHE.get(imageUrl,imageView)){
        imageView.setImageDrawable(null);
    }
}

其中setTag表示设置标识,方便下面进行标志比对

1
if (!Cache.ICON_CACHE.get(imageUrl, imageView))

Cache.ICON_CACHE为ImageCache的实例,表示如果不在缓存内则设置drawable为null(当然你可以可以设置为你自己的默认资源),防止显示了之前某个行item的图片,解决了a. 行item图片显示重复问题。

 

在ImageCache的OnImageCallbackListener的onGetSuccess函数中添加

Java
1
2
3
4
5
6
7
8
9
10
11
publicvoidonGetSuccess(StringimageUrl,DrawableimageDrawable,Viewview,booleanisInCache){
    // can be another view child, like textView and so on
    if(view!=null&&imageDrawable!=null){
        ImageViewimageView=(ImageView)view;
        // add tag judge, avoid listView cache and so on
        StringimageUrlTag=(String)imageView.getTag();
        if(ObjectUtils.isEquals(imageUrlTag,imageUrl)){
            imageView.setImageDrawable(imageDrawable);
        }
    }
};

在上面用String imageUrlTag = (String)imageView.getTag();取得之前设置的tag,然后和当前的url进行比较,如果相等则显示,解决了b. 行item图片显示错乱,c. 行item图片显示错乱的两个问题。其中ObjectUtils可见ObjectUtils@Github.

其他异步加载过程解决原理类似。

插一句:标题上我加了此文章的原文链接。


大家通过上面的文字可以发现,混乱的本质原因是由于使用了ViewHolder以及ListView的Item的缓存机制。所以在解决本类问题时也就分为两种方法:

(1)把上文中if(convertView==null){}else{holder=(ViewHolder)convertView.getTag() }给注释 掉,然后数据混乱的问题就得以解决了,但是这样问题虽然解决了,应用程序性能就降低了,显然此种方法虽能解决问题,但是还是不建议使用。

(2)就是通过添加Tag标记,但是虽然是说添加Tag标记,每一个应用程序添加Tag的方法不同,故在此无法给出一个统一的解决方法,只能说一个统一的解决思路。例如:如果混乱的是一件商品的数量,可以把商品的数量临时存到一个数组中,然后每次取值都从数组里取值就行了。其实说白了,就是根据具体的情况new 一个相应的数组,来做数据的中转站。如下图情形:

在滑动时,我们会发现数量的值会发生混乱,或者在加减右图中的数字时,也会出现这样的情况。

下面提供一个类似的方案,其实相应的代码都是在你应用中的Adapter中修改。

首先声明一个数组用于存放数量:

private int [] tempNum;

然后在本Adapter的构造方法中初始化此数组:tempNum=new int[this.list.size()];

然后在getView方法中一定要有如下代码:mItemViewHolder.mTvNum.setTag(position);

mItemViewHolder.mTvNum.setText(tempNum[position]+"");

然后在加减时,也要把最新的数量存到数组里,如下:

<span style="white-space:pre">		</span>case R.id.menu_reduce_iv:
			mTextView=(TextView)v.getTag(R.id.menu_num_tv);
			num=Integer.valueOf(mTextView.getText().toString().trim());
			
			num--;
			tempNum[(Integer)mTextView.getTag()]=num;
			
			if(num<0)
			{
				Toast.makeText(context, "亲,不能再减了,赶紧加加吧!", Toast.LENGTH_SHORT).show();
			}
			else
			{
				mTextView.setText( tempNum[(Integer)mTextView.getTag()]+"");
			}
			
			break;
		case R.id.menu_add_iv:
			mTextView=(TextView)v.getTag(R.id.menu_num_tv);
			num=Integer.valueOf(mTextView.getText().toString().trim());
			num++;
			tempNum[(Integer)mTextView.getTag()]=num;
			mTextView.setText(tempNum[(Integer)mTextView.getTag()]+"");
			
			break;

总之,在做此类处理时,一定要new一个数组,然后在混乱的地方一定要setTag。

大致就讲到这里,鄙人才疏学浅,有不对之处,望大家及时指出。转载请注明:http://blog.csdn.net/android_jiangjun/article/details/39924541


Android中ListView滑动时数据混乱