前言
使用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