ListView優(yōu)化(1),列表錯(cuò)亂問題

這節(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é)兩種情況就是:

  1. View1顯示圖片A,ListView滾動(dòng),View1被復(fù)用用于顯示圖片B,而圖片B加載不成功,此時(shí)View1仍然顯示的是圖片A。
  2. 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)生原因以及解決方法啦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容