原因就是:
1. 使用convertView可以避免重复地调用inflate
2. 使用ViewHolder可以避免重复地调用findViewById
首先讲下ListView的原理:ListView中的每一个Item显示都需要Adapter调用一次getView的方法,这个方法会传入一个convertView的参数,返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存,创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)也是耗时操作,所以必将影响性能。Android提供了一个叫做Recycler(反复循环器)的构件,就是当ListView的Item从上方滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从下方生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的Item的View,所以说如果能重用这个convertView,就会大大改善性能。
以下是最简单的使用viewHolder和convertView的代码
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
|
@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
(
)
;
}
}
|
这样提升了性能,但同时也会造成另外一些问题:
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行的图片,立马又显示了自己的图片进行覆盖造成闪烁错乱。
/**
* 显示这个图片,解决了列表问题.
* 列表问题:滑动过程中,getView的imageView会重复利用,导致图片会串位
* @param imageView 显得的View
* @param url the url
* @return
*/
public
void
display(
final
ImageView imageView,String url) {
if
(AbStrUtil.isEmpty(url)){
if
(noImage !=
null
){
if
(loadingView !=
null
){
loadingView.setVisibility(View.INVISIBLE);
imageView.setVisibility(View.VISIBLE);
}
imageView.setImageDrawable(noImage);
}
return
;
}
//设置下载项
final
AbImageDownloadItem item =
new
AbImageDownloadItem();
//设置显示的大小
item.width = width;
item.height = height;
//设置为缩放
item.type = type;
item.imageUrl = url;
final
String cacheKey = AbImageCache
.getCacheKey(item.imageUrl, item.width, item.height, item.type);
item.bitmap = AbImageCache.getBitmapFromCache(cacheKey);
//if(D) Log.d(TAG, "缓存中获取的"+cacheKey+":"+item.bitmap);
//设置标记
imageView.setTag(url);
if
(item.bitmap ==
null
){
//先显示加载中
if
(loadingView!=
null
){
loadingView.setVisibility(View.VISIBLE);
imageView.setVisibility(View.INVISIBLE);
}
else
if
(loadingImage !=
null
){
imageView.setImageDrawable(loadingImage);
}
//下载完成后更新界面
item.setListener(
new
AbImageDownloadListener() {
@Override
public
void
update(Bitmap bitmap, String imageUrl) {
//未设置加载中的图片,并且设置了隐藏的View
if
(loadingView !=
null
&& imageUrl.equals(imageView.getTag())){
loadingView.setVisibility(View.INVISIBLE);
imageView.setVisibility(View.VISIBLE);
}
//要判断这个imageView的url有变化,如果没有变化才set,
//有变化就取消,解决列表的重复利用View的问题
if
(bitmap!=
null
&& imageUrl.equals(imageView.getTag())){
if
(D) Log.d(TAG,
"图片下载,设置:"
+imageUrl);
imageView.setImageBitmap(bitmap);
}
else
{
if
(errorImage !=
null
&& imageUrl.equals(imageView.getTag())){
imageView.setImageDrawable(errorImage);
}
}
}
});
if
(D) Log.d(TAG,
"图片下载,执行:"
+url);
mAbImageDownloadPool.execute(item);
}
else
{
if
(loadingView !=
null
){
loadingView.setVisibility(View.INVISIBLE);
imageView.setVisibility(View.VISIBLE);
}
imageView.setImageBitmap(item.bitmap);
}
}
|
但是 在图片加载慢时,会出现listView显示空白现象,那就需要美工将公司logo设计成一个展示的item,默认展示公司logo,加载出图片就正常显示。