这节我们来讨论一下ListView的优化问题,ListView是我们在开发中非常常用的控件之一,而在开发中也经常会遇到关于ListView的问题,下面我们主要就ListView的两个主要问题进行分析和解决。
- ListView的列表错乱问题
- ListView的卡顿问题
一、ListView的列表错乱问题
1. 问题描述
在我们使用ListView异步加载很多图片的时候,会发现有时应该出现图片A的地方出现了图片B,或者某人的头像变成了其他人的头像,这就是ListView的列表错乱问题。
ListView列表错乱原因有两种情况。
2. 问题原因
原因就在于我们在Adapter的getView方法中复用了convertView。原本复用convertView的出发点是好的,是为了避免重复加载列表布局,也就是说本来用于加载显示图片A的列表项布局(这里称为View1)在图片A的位置移除到屏幕外之后,View1会被复用(也就是convertView)并用于加载新的图片B,从而避免了对View1重复加载列表项布局。
而问题就在于,如果图片B加载失败,没有覆盖复用的View1之前显示的图片A,或者另一种情况,View1在加载图片A完成前,View1已经被复用并完成加载显示图片B,此时图片A才完成加载并显示在View1上。这两种情况都会造成原本应该显示图片B的控件View1显示了图片A。这就是所谓的ListView图片错乱。
总结两种情况就是:
- View1显示图片A,ListView滚动,View1被复用用于显示图片B,而图片B加载不成功,此时View1仍然显示的是图片A。
- View1正在加载图片A,ListView滚动,View1被复用用于显示图片B,图片B很快加载显示,然后图片A才加载完并显示在View1中,此时图片A会把图片B覆盖,导致View1显示的是图片A。
3. 问题代码
复用convertView的BaseAdapter的getView方法
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vh = null;
if(convertView == null)
{
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list, parent, false);
vh = new ViewHolder(
((ImageView)convertView.findViewById(R.id.iv_uil)));
convertView.setTag(vh);
}else
{
vh = (ViewHolder) convertView.getTag();
}
//在子线程中异步加载图片,其中List包含图片的URL
loadImageAync(list.get(position),vh.imageView);
return convertView;
}
异步加载图片并在Handler中显示图片的代码:
public void loadImageAync(String uri, ImageView imageView)
{
//利用线程池加载图片
THREAD_POOL_EXECUTOR.execute(new Runnable()
{
@Override
public void run()
{
//通过url获取Bitmap
Bitmap bitmap = loadBitmap(uri);
if(bitmap != null)
{
//用一个封装类将uri,Bitmap和ImageView传送到Handler处理
LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
}
}
});
}
//用于在主线程中加载图片的Handler
Handler mMainHandler = new Handler(Looper.getMainLooper())
{
@Override
public void handleMessage(Message msg)
{
LoaderResult result = (LoaderResult) msg.obj;
ImageView imageView = result.getImageView();
Bitmap bitmap = result.getBitmap();
//显示图片到imageView中
imageView.setImageBitmap(bitmap);
}
};
上面这些就是我们通常用的,在子线程中加载图片,然后通过Handler将图片加载到ImageView当中。也就是这段代码,造成了图片错乱的原因。
4. 解决方法
解决切入点
回顾一下两种造成图片错乱的原因,一个是由于图片B加载不成功导致图片A没有被刷新,另一个是图片A加载时间长把已经加载完成的图片B覆盖了。两种原因不一样,因此要完全解决图片错乱的问题需要分别从两个问题原因入手。
第一种问题的解决方法
对于第一种情况,通常有这样的解决思路,那就是不管后面的图片B能不能加载成功,我在开始加载图片前我都对ImageView设置一个正在加载默认图片,这样即使后面的图片B加载失败,那显示的至少也是一个正在加载的图片(还可以在加载失败时显示失败图片),而不是图片A,这样就能避免了图片A显示在应该显示图片B的View上了。
这个方法能够解决第一种问题,通常这也就够了,因为大部分发生列表错乱的原因就是因为后面的图片加载不出来。
具体的实现就是,在getView中或者在loadImageAsync中异步加载图片前,给ImageView设置一张正在加载图片,更好的还需要在获取图片失败时将图片设置成一张失败图片,所以有两点需要改进。
第一种问题的解决方法:
public void loadImageAync(final String uri, final ImageView imageView)
{
//1.在加载图片之前设置一个默认图片
imageView.setImageDrawable(R.drawable.ic_loading);
THREAD_POOL_EXECUTOR.execute(new Runnable()
{
@Override
public void run()
{
Bitmap bitmap = loadBitmap(uri);
//注意判断条件改变了
if(bitmap == null)
{
//2. 加载图片失败时设置一个失败图片
bitmap = getDefaultErrorBitmap();
}
LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
}
});
}
第二种问题的解决方法
第二中问题就比较特殊了,根本原因就是,图片A不知道自己在绑定的ImageView中已经过期了
,用来显示自己的ImageView已经被复用了,但是图片A还是不要脸的在加载完成之后不管不顾的显示到ImageView中,如果图片B加载的比A久,那么不会出现问题(会出现图片A闪一下然后立刻换成B),如果B加载得快,那么最后图片A就会把图片B给覆盖。
既然问题找出来了,那么解决方法就好想了,只要让ImageView知道图片A已经过期
,不显示图片A就行了。
实现的方法就是,在加载图片A前,给ImageView设置一个标签,用于表示当前应该显示的图片A的uri。然后在图片A加载完成之后显示图片A前,再次验证ImageView的标签是否还和所加载的图片A的uri一致,这里标签会发生变化的原因就在于,ImageView被复用用于加载另一张图片B时,标签就会标称图片B的uri。如果最后uri一致,说明ImageView还没有被复用,然后便可以心安理得的显示图片A;如果不一致,说明ImageView已经被复用啦,已经不属于图片A的啦,此时就不要再显示A啦。
在代码中的实现主要分为两步,一是在加载图片之前为ImageView添加Tag,二是在Handler的handleMessage中显示图片前验证uri是否一致,一致的话才显示图片。以下是图片错乱的解决方法,包括了第一种问题的解决办法。
public void loadImageAync(final String uri, final ImageView imageView)
{
imageView.setImageDrawable(R.drawable.ic_loading);
//在加载之前为ImageView设置Tag为uri
imageView.setTag(uri);
THREAD_POOL_EXECUTOR.execute(new Runnable()
{
@Override
public void run()
{
Bitmap bitmap = loadBitmap(uri);
//注意判断条件改变了
if(bitmap == null)
{
bitmap = getDefaultErrorBitmap();
}
LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
}
});
}
Handler mMainHandler = new Handler(Looper.getMainLooper())
{
@Override
public void handleMessage(Message msg)
{
LoaderResult result = (LoaderResult) msg.obj;
ImageView imageView = result.getImageView();
Bitmap bitmap = result.getBitmap();
String uri = result.getUri();
//如果uri一致才显示图片到imageView中
if(uri.equals((String)imageView.getTag()))
{
imageView.setImageBitmap(bitmap);
}else
{
Log.w(TAG,"set image bitmap, but uri has changed, ignored!");
}
}
};
以上就是ListView列表错乱的产生原因以及解决方法啦~