主要分析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的个数。
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中添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@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(); } …… // 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函数中添加
1 2 3 4 5 6 7 8 9 10 11 |
public void onGetSuccess(String imageUrl, Drawable imageDrawable, View view, boolean isInCache) { // can be another view child, like textView and so on if (view != null && imageDrawable != null) { ImageView imageView = (ImageView)view; // add tag judge, avoid listView cache and so on String imageUrlTag = (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.
其他异步加载过程解决原理类似。
Cache.ICON_CACHE,查看了你的Cache源码,发现并没有ICON_CACHE属性啊
ICON_CACHE 是一个 ImageCache 的实例 http://www.trinea.cn/android/android-imagecache%e5%9b%be%e7%89%87%e7%bc%93%e5%ad%98%ef%bc%8c%e4%bd%bf%e7%94%a8%e7%ae%80%e5%8d%95%ef%bc%8c%e6%94%af%e6%8c%81%e9%a2%84%e5%8f%96%ef%bc%8c%e6%94%af%e6%8c%81%e5%a4%9a%e7%a7%8d%e7%bc%93/
这样做imageview就不在viewholder里面了,那就没有复用convertview了
有
问问Trinea,为什么文字(TextView)不会出现这样的问题?
上面已经提到原因了,ImageView 的异步加载造成的,TextView 正常情况下不会出现这种问题
1
Pingback: Android Universal Image Loader 源码分析 | 数据魔方
Pingback: Android Universal Image Loader 源码分析[转] – 酷码
LZ:我也遇到这个问题,在文件管理器中,一个目录下包含多个子目录与多个图片。快速滑动时,子目录的图标变成了图片的缩略图。请问是否有遇到过这个现象?getView 的开头加一句convertView = null,无此现象。但是这样item多的时候,这个方法显示不适用。
明显没仔细看文章…
没有使用ImageCache这个库,且代码也已经设置了 tag, imageView.setTag(file.getPath());图片是没有显示错乱的,但是非图片的文件夹图标显示错乱了。对于你说的 ”在异步加载完成时比较标识与当前行item的标识是否一致,一致则显示,否则不做处理即可“,是对于图片的处理,判断如果是图片类型的,才会走到异步加载的流程,从而取到tag,判断。但是我在getView中有相关判断,如果是目录,就直接返回convertView了,不会走到异步加载的流程
因为复用的关系,子目录显示某个图片的缩略图很正常啊,判断是子目录后,手动设置为目录图标就好
判断是子目录后,有手动设置目录图标,然后再返回convertView。对于复用的关系,子目录显示某个图片的缩略图 这一现象 ,是否有解决建议呢?
按理说你手动设置应该就不存在这类现象。对于子目录,你现在是先显示目录图标再立即显示图片缩略图,还是说先显示图片缩略图
之前的术可能有误点误差,是子目录显示成图片的预览图。我现在是先显示目录,设置目录图标,目录全部显示完了,接着其他文件,设置文件默认图标。然后开始判断如果是图片类型,解析图片,以预览图的形式显示,解析时有取tag判断,同时放入缓存。我录了一个视频,可以发给你看看
Pingback: Android Universal Image Loader 源码分析 | 技术联盟
UIL设置了tag,不管用。
Pingback: Universal ImageLoader源码解析 | 风语的博客