RecyclerView源碼學習筆記(二)setAdapter

引言

上篇文章RecyclerView源碼學習筆記(一)構造函數和setLayoutManager方法主要學習了RecyclerView初始化和setLayoutManager方法的源碼,這篇我們學習setAdapter方法的源碼

內容

setAdapter方法

按照我們平時最簡單的使用習慣,在調用完setLayoutManager方法之後就要調用setAdapter方法了,直接貼源碼

    /**
     * Set a new adapter to provide child views on demand.
     * <p>
     * When adapter is changed, all existing views are recycled back to the pool. If the pool has
     * only one adapter, it will be cleared.
     *
     * @param adapter The new adapter to set, or null to set no adapter.
     * @see #swapAdapter(Adapter, boolean)
     */
    public void setAdapter(@Nullable Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        requestLayout();
    }

代碼不多,從註釋來看這個方法的作用是

  • 設置一個新的Adapter
  • 所有已經存在的view將會被回收到pool中,如果pool只有一個adapter,那麼這個pool將會被清空

上面提到的pool就是在前一篇RecyclerView源碼學習筆記(一)構造函數和setLayoutManager方法中說的RecycledViewPool。
接下來一行一行看下去。第一行是調用了setLayoutFrozen(false),這個方法的作用是什麼呢?從註釋來看可以將此方法歸納爲以下幾點:

  • 決定RecyclerView是否可以進行layout和scroll,如果參數是true,相當於這個RecyclerView被凍住了,那麼layout的請求將被推遲,直到調用setLayoutFrozen(false)
  • 當RecyclerView被凍住的時候,RecyclerView的smoothScrollByscrollByscrollToPositionsmoothScrollToPosition這些方法的調用將直接被丟棄,也就是直接返回,這個我們可以看一下scrollBy的源碼,當判斷到mLayoutFrozen等於true的時候就直接返回了,而這個mLayoutFrozen就是在setLayoutFrozen方法中被賦值的。
    public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator) {
        if (mLayout == null) {
            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
                    + "Call setLayoutManager with a non-null argument.");
            return;
        }
        if (mLayoutFrozen) {
            return;
        }
        if (!mLayout.canScrollHorizontally()) {
            dx = 0;
        }
        if (!mLayout.canScrollVertically()) {
            dy = 0;
        }
        if (dx != 0 || dy != 0) {
            mViewFlinger.smoothScrollBy(dx, dy, interpolator);
        }
    }
  • 另外RecyclerView的TouchEvents 和 GenericMotionEvents事件也會被丟棄,LayoutManager的onFocusSearchFailed不會被調用,但是LayoutManager的scrollToPositionsmoothScrollToPosition並不受影響,還可以照常運行。
    -setAdapter方法和swapAdapter方法會自動結束冰凍狀態,就是會調用setLayoutFrozen(false)
  • 任何正在跑的ItemAnimator不會自動停止,需要調用者去手動停止。

寫了這麼多,還不如直接看源碼來的實在,那麼就上源碼:

public void setLayoutFrozen(boolean frozen) {
        if (frozen != mLayoutFrozen) {
            assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
            if (!frozen) {
                mLayoutFrozen = false;
                if (mLayoutWasDefered && mLayout != null && mAdapter != null) {
                    requestLayout();
                }
                mLayoutWasDefered = false;
            } else {
                final long now = SystemClock.uptimeMillis();
                MotionEvent cancelEvent = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                onTouchEvent(cancelEvent);
                mLayoutFrozen = true;
                mIgnoreMotionEventTillDown = true;
                stopScroll();
            }
        }
    }

首先會判斷當前狀態和目標狀態是否一樣,一樣的話就直接返回了,所以我們平時在使用的時候就不需要再自己去判斷了。
當然我們這裏肯定是要不一樣的,所以繼續往下看,assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll"),這句的作用是判斷當前RecyclerView是不是在layout或者scroll,如果是這兩種狀態,那麼就直接拋出異常,也就是說,不能在layout或者scroll的時候去更新adapter。

然後如果目標狀態是解凍,那麼就mLayoutFrozen設置爲false,然後看mLayoutWasDefered是不是true,也就是有沒有layout請求被延遲了,如果有就調用requestLayout(),並將mLayoutWasDefered設置爲false。如果目標狀態是凍住RecyclerView,那麼就生成一個cancelEvent,並且傳遞給onTouchEvent方法,並將mLayoutFrozen設置爲true,最後調用stopScroll()也就是停止RecyclerView的滑動。

setLayoutFrozen講完了,回到setAdapter方法源碼

    public void setAdapter(@Nullable Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        requestLayout();
    }

接下來是調用setAdapterInternal(adapter, false, true),源碼如下

 private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
    }

主要做了以下事情:

  • 判斷mAdapter是不是null,也就是舊的adapter是不是存在,如果存在則註銷AdapterDataObserver,並調用onDetachedFromRecyclerView,這個方法的默認實現是空的。
  • 判斷兩個參數:compatibleWithPreviousremoveAndRecycleViews,這兩個參數是在調用setAdapterInternal的時候傳進來的,前者表示新的adapter使用的viewholder和itemtype和舊的adapter是一樣的,後者表示需不需要將所有已經存在view移除,並回收。在這裏compatibleWithPreviousfalseremoveAndRecycleViewstrue,所以我們需要調用removeAndRecycleViews()removeAndRecycleViews()就不講了,很簡單,如果看過前面的文章RecyclerView源碼學習筆記(一)構造函數和setLayoutManager方法,就可以輕鬆閱讀源碼。
  • 調用mAdapterHelper.reset(),重置AdapterHelper,就是將還未完成的item操作,比如move,add等都刪除。
  • 將舊的adapter賦值給oldAdapter,將新的adapter賦值給mAdapter,並註冊AdapterDataObserver,調用onAttachedToRecyclerViewonAttachedToRecyclerView默認實現也是空的。
  • 如果LayoutManager不是null,則調用mLayout.onAdapterChanged(oldAdapter, mAdapter),這個方法他們默認實現也是空方法,且SDK中的LayoutManager的子類好像都沒有重寫這個方法。
  • 調用mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious),源碼如下
  void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
          boolean compatibleWithPrevious) {
      clear();
      getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
  }

先是調用clear方法,該方法做的事情是清空mAttachedScap,將mCachedViews中的view放到pool中,並清空mCachedViews。然後調用RecycledViewPool的onAdapterChanged方法,源碼如下

  void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
          boolean compatibleWithPrevious) {
      if (oldAdapter != null) {
          detach();
      }
      if (!compatibleWithPrevious && mAttachCount == 0) {
          clear();
      }
      if (newAdapter != null) {
          attach(newAdapter);
      }
  }

做了以下事情:

  • 如果oldAdapter不爲null,則調用detach()方法,這個detach方法內部很簡單,只是把mAttachCount減一操作,這個mAttachCount記錄了當前有多少個RecyclerView和這個pool建立了關係,因爲多個RecyclerView可以共用同一個pool,共用緩存的view,前提是這些view的viewtype要一樣。
  • 如果compatibleWithPreviousfalsemAttachCount等於0,則清空pool。
  • 如果newAdapter不爲null,則mAttachCount加一

可以看到這個方法內部還是很簡單的。
回到setAdapterInternal方法,最後將mState.mStructureChanged設置爲true
然後再回到setAdapter方法,接下來會調用processDataSetCompletelyChanged(false),這個方法的註釋我也不看出所以然來,就直接上代碼:

    void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
        mDispatchItemsChangedEvent |= dispatchItemsChanged;
        mDataSetHasChangedAfterLayout = true;
        markKnownViewsInvalid();
    }
  • mDispatchItemsChangedEventdispatchItemsChanged進行或運算,把結果賦值給mDispatchItemsChangedEvent,這個值決定在RecyclerView進行measure和layout時候要不要LayoutManager的onItemsChanged(RecyclerView)方法。
  • mDataSetHasChangedAfterLayout設置true
  • 調用markKnownViewsInvalid(),從方法註釋來看是將所有view標示爲invalid,還是看源碼實在
     void markKnownViewsInvalid() {
        final int childCount = mChildHelper.getUnfilteredChildCount();
        for (int i = 0; i < childCount; i++) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.shouldIgnore()) {
                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
            }
        }
        markItemDecorInsetsDirty();
        mRecycler.markKnownViewsInvalid();
    }
  • 將所有childview(包括不可見的)的viewholder添加ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID標籤,標示viewholder已經不可用了
  • 調用markItemDecorInsetsDirty()將所有childview(包括不可見的)的LayoutParam中的mInsetsDirty設置爲true,並將mCachedView中的view的LayoutParam中的mInsetsDirty也設置爲true,標示ItemDecorInset也需要更新了,而這個ItemDecorInset就是一個矩形,它的值就是我們在重寫ItemDecorator的時候需要重寫的getItemOffsets方法中設置的。
  • 調用mRecycler.markKnownViewsInvalid(),方法內部是將所有mCachedView中的view的viewholder添加ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID標籤,如果新的adapter爲null,則直接將所有mCachedView中的view放到pool中,並清空mCachedView。如果有與抓取,就把與抓取的view也清空

到這裏processDataSetCompletelyChanged方法的源碼就看完了,接下來回到setAdapter方法,直接調用了requestLayout來進行重新佈局。

setAdapter方法就看完了,主要工作還是清理工作另外就是啓動重啓佈局計算,好了,下一篇文章就開始看佈局的過程

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