亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android中RecyclerView復用錯亂的示例分析

發布時間:2021-08-11 10:25:00 來源:億速云 閱讀:154 作者:小新 欄目:移動開發

這篇文章給大家分享的是有關Android中RecyclerView復用錯亂的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

復用錯亂的解決辦法

本文的前半部分很簡單的,以為關于復用錯亂,RecyclerView 已經有他的前輩 ListView 替它踩了很多坑了。雖然他們的復用邏輯是有差異的,例如 ListView 只有兩層緩存,但是 RecyclerView 可以理解為有四層;ListView 緩存的單位是 view,而 RecyclerView 緩存的單位是 ViewHolder。但是不管他們復用邏輯的差異如何,終歸都是把那個緩存起來的 view 拿過來接著用,所以解決復用錯亂的方法是一樣的。

RecyclerView 復用導致錯亂的原因其實就是拿出來之前的 View 來添加到新 item 上,之前 View 的狀態一直保留著,所以也就錯亂了。不過解決起來很簡單:

首先我們以 adapter 數據的來源分為兩大類:

1.當數據來源是同步的

這種情況是最簡單的,你就保證當 onBindViewHolder 方法調用的時候,你的 itemview 中每個 view 的狀態都有一個默認值。這是什么意思呢?

if ("<unknown>".equals(artists)) {
      holder.cbMusicState.setChecked(true);
    } else {
      holder.cbMusicState.setChecked(false);
    }

假設我們的 holder 里面有個 Checkbox 控件,當歌手名為 unknown 時,Checkbox 勾選。注意個時候你一定要加上這個 else 條件,才能保證復用這個 ViewHolder 的時候,Checkbox 的狀態不出錯。任何控件都一樣,總結起來就是你要給每個控件的狀態賦一個新的值,替換掉之前的,這樣自然不會出現什么復用錯亂的問題。

2.當數據的來源是異步的

這種情況也很常見,我們舉個栗子,比如你的 ItemView 里面有個 ImageView,每次 onBindViewHolder 的時候,你傳入一個 url,等待服務器返回的結果,然后展示在 ImageView 上。這種情況會怎樣導致錯亂呢?

是這樣的,假設我進入了頁面,開始為第一個 ImageView 請求圖片,但是此刻我下劃屏幕,劃到了第四個 item,此時第一個 item 已經不可見了,第四個 item 復用了第一個 item 的 imageview,恰好此刻第一個 imageview 的圖片結果返回了,就正好展示在了第四個 itemview 上。 這樣就發生了圖片的錯亂。

出現這個問題的原因就是這個 ImageView 和請求的 url 沒一一綁定,所以按照這個思路來解決吧:

  holder.ivCameraImages.setBackground(R.drawable.place_holder);
  
  holder.ivCameraImages.setTag(imageURL);

    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      if (msg.what == MSG_IMAGE) {
        Bitmap bm = (Bitmap) msg.obj;
        if (bm != null) {
          if (TextUtils.equals((String) imageView.getTag(), imageURL)) {
            imageView.setBackground(new BitmapDrawable(bm));
          }
        }
      }
    }

首先在沒加載圖片之前,給 ImageView 設置一個默認圖片,然后通過 setTag 方法,將 ImageView 和 圖片的 url 一一對應起來,設置的時候再判斷一下,這個 imageview 的 tag 和當時請求的 url,是不是一致的,如果是一致的,再保存。

以上就是復用錯亂時兩種比較通用的解法,基本上可以覆蓋大部分情況。

一個奇怪的問題

這個問題的現象是這樣子的:

當 RecyclerView 的條目很少的時候,比如只有六個,將 RecyclerView 從上滑動到下,這個時候是正常的,onBindViewHolder 會調用,不過此時從底部上劃的時候,上方的 item 從不可見到可見的這個過程中,onBindViewHolder 并沒有調用,這個時候我也就沒辦法進行一些刷新 item 的操作了。

這個問題的原因是 onBindViewHolder 方法不調用導致的,我在 StackOverflow 上搜索了很多答案,終于找到了一個可以解決我的問題的:

recyclerview-not-recycling-views-if-the-view-count-is-small

(中文資料壓根就沒有,所以掌握英文搜索是多么的重要)

你可以調用

recyclerView.setItemViewCacheSize(int);

這個 api,去調整 RecyclerView 的復用邏輯和方式來解決 onBindViewHolder 沒有調用的這個問題。

但是原理是怎樣的呢?作為一名好奇心頗重的程序員,一步步 debug RecyclerView 的源代碼,發現了導致這個問題的原因,一起來看看吧。

在上一篇文章中,我們分析了 RecyclerView 的源碼,其中復用邏輯的模塊,有一個非常重要的核心方法 tryBindViewHolderByDeadline,這個方法目的就是在 RecyclerView 的層層緩存結構中,取出 ViewHolder。

這里就不再次研究它了,想了解的去看之前的文章,我來描述一下對于這個場景,簡化之后的邏輯:

當 RecyclerView 從底部向上滑動的時候,會先后從 mCachedViews 和 mRecyclerPool 中尋找緩存的 ViewHolder。

mCachedViews 和 mRecyclerPool 之間又有什么關系呢?

public void setViewCacheSize(int viewCount) {
      mRequestedCacheMax = viewCount;
      updateViewCacheSize();
    }

    void updateViewCacheSize() {
      int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
      mViewCacheMax = mRequestedCacheMax + extraCache;

      // first, try the views that can be recycled
      for (int i = mCachedViews.size() - 1;
          i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
        recycleCachedViewAt(i);
      }
    }

當調用 setViewCacheSize 這個方法時,相當于是給 mViewCacheMax 這個變量賦值了, for 循環調用 recycleCachedViewAt 的作用是將 mCachedViews 中緩存的 ViewHolder 放進 RecyclerPool 中。可以看到 for 循環的周期是從 mCachedViews 的最后一個對象直到 mCachedViews.size == mViewCacheMax 這個值時。

也就是可以這么理解, setViewCacheSize 這個方法其實就是為 mCachedViews 集合設置所能持有 ViewHolder 的最大數量。

當  setViewCacheSize(0)時,RecyclerView 想去復用 ViewHolder 時,只能去 RecyclerPool 中去取了,這里就有問題來了,從 RecyclerPool 中取和從 mCachedViews 中取 ViewHolder 中又有什么區別呢?

if (holder == null) { // fallback to pool
          if (DEBUG) {
            Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                + position + ") fetching from shared pool");
          }
          holder = getRecycledViewPool().getRecycledView(type);
          if (holder != null) {
            holder.resetInternal();
            if (FORCE_INVALIDATE_DISPLAY_LIST) {
              invalidateDisplayListInt(holder);
            }
          }
        }

當從 RecyclerPool 取出 ViewHolder 時,調用了 resetInternal 這個函數的作用是清空一些記錄的參數,包括之前記錄 ViewHolder 狀態的 mFlags。

 else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        if (DEBUG && holder.isRemoved()) {
          throw new IllegalStateException("Removed holder should be bound and it should"
              + " come here only in pre-layout. Holder: " + holder);
        }
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
      }

代碼再往下走的時候,剛剛清空的 flag 參數這個時候就用到了,holder.isBound() 返回 flase,進入 if 判斷,調用 tryBindViewHolderByDeadline 進而調用了 onBindViewHolder

到這里這個邏輯就描述清楚了,所以設置 setViewCacheSize 來調整 mCachedViews 保存 ViewHolder 的大小,就能解決問題。

當然有些特殊的情況,某些位置就不能調用 onBindViewHolder,沒關系,可以監聽 RecyclerView 的滑動,當滑動停止的時候,再調用 notify 刷新下列表也是可以的。

感謝各位的閱讀!關于“Android中RecyclerView復用錯亂的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

武平县| 东丰县| 禹城市| 通许县| 沅江市| 双牌县| 新田县| 乌兰浩特市| 汝阳县| 探索| 资兴市| 贡嘎县| 琼结县| 新安县| 敦煌市| 上虞市| 盐亭县| 高台县| 雷波县| 遵化市| 石家庄市| 麻栗坡县| 满洲里市| 大埔县| 广东省| 宜宾市| 马龙县| 汉阴县| 平安县| 平武县| 汶上县| 沅江市| 清水河县| 甘谷县| 四子王旗| 航空| 龙州县| 道孚县| 登封市| 深水埗区| 棋牌|