關於RecyclerView使用Glide加載圖片導致錯位問題總結

前言

使用recyclerView時遇到了圖片錯位的問題,這個問題網上已經討論的很成熟,謹以此文章做個總結。

問題產生原因

根本原因: 因爲有ViewHolder的重用機制,每一個item在移出屏幕後都會被重新使用以節省資源,避免滑動卡頓。

場景A:
1.第一次進入頁面,RecyclerView載入,不做任何觸摸操作
2.Adapter經過onCreateViewHolder()創建當前顯示給用戶的N個ViewHolder對象,並且在onBind時啓動了N條線程加載圖片
3.N張圖片全部加載完畢,並且顯示到對應的ImageView上
4.控制屏幕向下滑動,前K個item離開屏幕可視區域,後K個item進入屏幕可視區域
5.前K個item被回收,重用到後K個item。後K個item顯示的圖片是前K個item的圖片
6.開啓了K條線程,加載後K張圖片。等待幾秒,後K個item顯示的圖片突然變成了正確的圖片

經過細化分析可以看出:如果當前網絡速度很快,第6個步驟的加載速度在1秒甚至0.5秒內,就會造成人眼看到的圖片閃爍問題,後K個item的圖片閃了一下變成了正確的圖片。

場景B:
1.第一次進入頁面,RecyclerView載入,不做任何觸摸操作
2.Adapter經過onCreateViewHolder()創建當前顯示給用戶的N個ViewHolder對象,並且在onBind時啓動了N條線程加載圖片
3.結果N張圖片全部加載完畢,並且顯示到對應的ImageView上,但還有1張未加載完(假設是第一張圖片未加載完)
4.控制屏幕向下滑動動,前K個item離開屏幕可視區域,後K個item進入屏幕可視區域
5.前K個item被回收,重用到後K個item。場景A的問題不再說,後K張圖片加載完畢(看上去一切正常)
6.等待幾秒,第一張圖片終於加載完成,後K個item中的某一個突然從正確的圖片(當前positon應
該顯示的圖片)變成不正確的圖片(第一個item的圖片)

以上過程是場景B,問題出在加載第一張圖片的線程T,持有了item1的ImageView對象引用,而這張圖片加載速度非常慢,直到item1已經被重用到後面item後,過了一段時間,線程T才把圖片一加載出來,並設置到item1的ImageView上,然而線程T並不知道item1已經不存在且已複用成其他item,於是,圖片發生錯亂了。

場景C:
1.第一次進入頁面,RecyclerView載入,不做任何觸摸操作
2.Adapter經過onCreateViewHolder()創建當前顯示給用戶的N個ViewHolder對象,並且在onBind時啓動了N條線程加載圖片
3.忽略圖片加載情況,直接向下滾動,再向上滾動,再向下滾動,來回操作
4.由於離開了屏幕的item是隨機被回收並重用的,所以向下滾動時我們假設item1、item3被回收重用到item9、item10,item2、item4被回收重用到item11、item12
5.向上滾動時,item9、item12被回收重用到item1、item2,item10、item11被回收重用到item3、item4
6.多次上下滾動後,停下,最後發現某一個item的圖片在不停變化,最後還不一定是正確的圖片

以上過程是場景C,問題出現在ViewHolder的回收重用順序是隨機的,回收時會從離開屏幕範圍的item中隨機回收,並分配給新的item,來回操作數次,就會造成有多條加載不同圖片的線程,持有同一個item的ImageView對象,造成最後在同一個item上圖片變來變去,錯亂更加嚴重。

解決方案

一、設置佔位圖

Glide有兩種方法設置佔位圖

1、直接在鏈式請求中加.placeholder()

Glide.with(this)
        .load(picUrl)
        .placeholder(R.drawable.ic_loading)
        .into(holder.ivThumb)

2、添加監聽,在回調方法中設置:

Glide.with(mContext)
     .load(picUrl)
     .error(R.drawable.ic_loading)
     .into(new SimpleTarget<GlideDrawable>() {
         @Override
         public void onResourceReady(GlideDrawable glideDrawable, GlideAnimation<? super  GlideDrawable> glideAnimation) {
                     holder.ivThumb.setImageDrawable(glideDrawable);
         }

         @Override
         public void onStart() {
             super.onStart();
             holder.ivThumb.setImageResource(R.drawable.ic_loading);
         }
     });

二、設置TAG

使用setTag()方式。但是,Glide圖片加載也是使用這個方法,所以需要使用setTag(key,value)方式進行設置,這種方式是不錯的一種解決方式,注意取值的時候應該是getTag(key)這個方法,當異步請求回來的時候對比下tag是否一樣,再判斷是否顯示圖片,我使用的是position設置tag.

時間及事件梳理
在這裏插入圖片描述

代碼

 @Override
    public void onBindViewHolder(final VideoViewHolder holder, final int position) {
        holder.thumbView.setTag(R.id.tag_dynamic_list_thumb, position);
        Glide.with(mContext)
                .load(picUrl)
                .error(R.drawable.video_thumb_loading)
                .into(new SimpleTarget<GlideDrawable>() {
                    @Override
                    public void onResourceReady(GlideDrawable glideDrawable, GlideAnimation<? super GlideDrawable> glideAnimation {
                        if (position != (Integer) holder.thumbView.getTag(R.id.tag_dynamic_list_thumb))
                            return;
                            holder.thumbView.setImageDrawable(glideDrawable);
                    }

                    @Override
                    public void onStart() {
                        super.onStart();
                        holder.thumbView.setImageResource(R.drawable.ic_loading);
                    }
            });
    }

三、在onViewRecycled方法中重置item的ImageView並取消網絡請求

流程:在onBindViewHolder中發起加載請求,然後在view被回收時取消網絡請求

代碼

@Override
public void onBindViewHolder(VideoViewHolder holder, int position) {
    String istrurl = mImgList.get(position).getImageUrl();
    if (null == holder || null == istrurl || istrurl.equals("")) {
        return;
    }
    Glide.with(mContext)
            .load(picUrl)
            .placeholder(R.drawable.ic_loading)
            .into(holder.thumbView);
}

@Override
public void onViewRecycled(VideoViewHolder holder) {
    if (holder != null) {
        Glide.clear(holder.thumbView);
        holder.thumbView.setImageResource(R.drawable.ic_loading);

    }
    super.onViewRecycled(holder);
}

最後感謝以下幾篇文章:
https://blog.csdn.net/lililijunwhy/article/details/79869491
https://blog.csdn.net/qq_33808060/article/details/59116624
https://blog.csdn.net/life90/article/details/78884618

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章