這節(jié)我們來討論一下ListView的優(yōu)化問題,ListView是我們?cè)陂_發(fā)中非常常用的控件之一,而在開發(fā)中也經(jīng)常會(huì)遇到關(guān)于ListView的問題,下面我們主要就ListView的兩個(gè)主要問題進(jìn)行分析和解決。
- ListView的列表錯(cuò)亂問題
- ListView的卡頓問題
一、ListView的列表錯(cuò)亂問題
1. 問題描述
在我們使用ListView異步加載很多圖片的時(shí)候,會(huì)發(fā)現(xiàn)有時(shí)應(yīng)該出現(xiàn)圖片A的地方出現(xiàn)了圖片B,或者某人的頭像變成了其他人的頭像,這就是ListView的列表錯(cuò)亂問題。
ListView列表錯(cuò)亂原因有兩種情況。
2. 問題原因
原因就在于我們?cè)贏dapter的getView方法中復(fù)用了convertView。原本復(fù)用convertView的出發(fā)點(diǎn)是好的,是為了避免重復(fù)加載列表布局,也就是說本來用于加載顯示圖片A的列表項(xiàng)布局(這里稱為View1)在圖片A的位置移除到屏幕外之后,View1會(huì)被復(fù)用(也就是convertView)并用于加載新的圖片B,從而避免了對(duì)View1重復(fù)加載列表項(xiàng)布局。
而問題就在于,如果圖片B加載失敗,沒有覆蓋復(fù)用的View1之前顯示的圖片A,或者另一種情況,View1在加載圖片A完成前,View1已經(jīng)被復(fù)用并完成加載顯示圖片B,此時(shí)圖片A才完成加載并顯示在View1上。這兩種情況都會(huì)造成原本應(yīng)該顯示圖片B的控件View1顯示了圖片A。這就是所謂的ListView圖片錯(cuò)亂。
總結(jié)兩種情況就是:
- View1顯示圖片A,ListView滾動(dòng),View1被復(fù)用用于顯示圖片B,而圖片B加載不成功,此時(shí)View1仍然顯示的是圖片A。
- View1正在加載圖片A,ListView滾動(dòng),View1被復(fù)用用于顯示圖片B,圖片B很快加載顯示,然后圖片A才加載完并顯示在View1中,此時(shí)圖片A會(huì)把圖片B覆蓋,導(dǎo)致View1顯示的是圖片A。
3. 問題代碼
復(fù)用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)
{
//用一個(gè)封裝類將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當(dāng)中。也就是這段代碼,造成了圖片錯(cuò)亂的原因。
4. 解決方法
解決切入點(diǎn)
回顧一下兩種造成圖片錯(cuò)亂的原因,一個(gè)是由于圖片B加載不成功導(dǎo)致圖片A沒有被刷新,另一個(gè)是圖片A加載時(shí)間長(zhǎng)把已經(jīng)加載完成的圖片B覆蓋了。兩種原因不一樣,因此要完全解決圖片錯(cuò)亂的問題需要分別從兩個(gè)問題原因入手。
第一種問題的解決方法
對(duì)于第一種情況,通常有這樣的解決思路,那就是不管后面的圖片B能不能加載成功,我在開始加載圖片前我都對(duì)ImageView設(shè)置一個(gè)正在加載默認(rèn)圖片,這樣即使后面的圖片B加載失敗,那顯示的至少也是一個(gè)正在加載的圖片(還可以在加載失敗時(shí)顯示失敗圖片),而不是圖片A,這樣就能避免了圖片A顯示在應(yīng)該顯示圖片B的View上了。
這個(gè)方法能夠解決第一種問題,通常這也就夠了,因?yàn)榇蟛糠职l(fā)生列表錯(cuò)亂的原因就是因?yàn)楹竺娴膱D片加載不出來。
具體的實(shí)現(xiàn)就是,在getView中或者在loadImageAsync中異步加載圖片前,給ImageView設(shè)置一張正在加載圖片,更好的還需要在獲取圖片失敗時(shí)將圖片設(shè)置成一張失敗圖片,所以有兩點(diǎn)需要改進(jìn)。
第一種問題的解決方法:
public void loadImageAync(final String uri, final ImageView imageView)
{
//1.在加載圖片之前設(shè)置一個(gè)默認(rèn)圖片
imageView.setImageDrawable(R.drawable.ic_loading);
THREAD_POOL_EXECUTOR.execute(new Runnable()
{
@Override
public void run()
{
Bitmap bitmap = loadBitmap(uri);
//注意判斷條件改變了
if(bitmap == null)
{
//2. 加載圖片失敗時(shí)設(shè)置一個(gè)失敗圖片
bitmap = getDefaultErrorBitmap();
}
LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
}
});
}
第二種問題的解決方法
第二中問題就比較特殊了,根本原因就是,圖片A不知道自己在綁定的ImageView中已經(jīng)過期了,用來顯示自己的ImageView已經(jīng)被復(fù)用了,但是圖片A還是不要臉的在加載完成之后不管不顧的顯示到ImageView中,如果圖片B加載的比A久,那么不會(huì)出現(xiàn)問題(會(huì)出現(xiàn)圖片A閃一下然后立刻換成B),如果B加載得快,那么最后圖片A就會(huì)把圖片B給覆蓋。
既然問題找出來了,那么解決方法就好想了,只要讓ImageView知道圖片A已經(jīng)過期,不顯示圖片A就行了。
實(shí)現(xiàn)的方法就是,在加載圖片A前,給ImageView設(shè)置一個(gè)標(biāo)簽,用于表示當(dāng)前應(yīng)該顯示的圖片A的uri。然后在圖片A加載完成之后顯示圖片A前,再次驗(yàn)證ImageView的標(biāo)簽是否還和所加載的圖片A的uri一致,這里標(biāo)簽會(huì)發(fā)生變化的原因就在于,ImageView被復(fù)用用于加載另一張圖片B時(shí),標(biāo)簽就會(huì)標(biāo)稱圖片B的uri。如果最后uri一致,說明ImageView還沒有被復(fù)用,然后便可以心安理得的顯示圖片A;如果不一致,說明ImageView已經(jīng)被復(fù)用啦,已經(jīng)不屬于圖片A的啦,此時(shí)就不要再顯示A啦。
在代碼中的實(shí)現(xiàn)主要分為兩步,一是在加載圖片之前為ImageView添加Tag,二是在Handler的handleMessage中顯示圖片前驗(yàn)證uri是否一致,一致的話才顯示圖片。以下是圖片錯(cuò)亂的解決方法,包括了第一種問題的解決辦法。
public void loadImageAync(final String uri, final ImageView imageView)
{
imageView.setImageDrawable(R.drawable.ic_loading);
//在加載之前為ImageView設(shè)置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列表錯(cuò)亂的產(chǎn)生原因以及解決方法啦~