引言
上篇文章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的
smoothScrollBy
,scrollBy
,scrollToPosition
,smoothScrollToPosition
這些方法的調用將直接被丟棄,也就是直接返回,這個我們可以看一下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的scrollToPosition
和smoothScrollToPosition
並不受影響,還可以照常運行。
-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;
}
主要做了以下事情:
- 判斷
mAdapte
r是不是null
,也就是舊的adapter是不是存在,如果存在則註銷AdapterDataObserver,並調用onDetachedFromRecyclerView
,這個方法的默認實現是空的。 - 判斷兩個參數:
compatibleWithPrevious
和removeAndRecycleViews
,這兩個參數是在調用setAdapterInternal
的時候傳進來的,前者表示新的adapter使用的viewholder和itemtype和舊的adapter是一樣的,後者表示需不需要將所有已經存在view移除,並回收。在這裏compatibleWithPrevious
是false
,removeAndRecycleViews
是true
,所以我們需要調用removeAndRecycleViews()
,removeAndRecycleViews()
就不講了,很簡單,如果看過前面的文章RecyclerView源碼學習筆記(一)構造函數和setLayoutManager方法,就可以輕鬆閱讀源碼。 - 調用
mAdapterHelper.reset()
,重置AdapterHelper,就是將還未完成的item操作,比如move,add等都刪除。 - 將舊的adapter賦值給oldAdapter,將新的adapter賦值給
mAdapter
,並註冊AdapterDataObserver,調用onAttachedToRecyclerView
,onAttachedToRecyclerView
默認實現也是空的。 - 如果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要一樣。 - 如果
compatibleWithPrevious
爲false
且mAttachCount
等於0,則清空pool。 - 如果
newAdapter
不爲null
,則mAttachCount
加一
可以看到這個方法內部還是很簡單的。
回到setAdapterInternal
方法,最後將mState.mStructureChanged
設置爲true
。
然後再回到setAdapter
方法,接下來會調用processDataSetCompletelyChanged(false)
,這個方法的註釋我也不看出所以然來,就直接上代碼:
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
mDispatchItemsChangedEvent |= dispatchItemsChanged;
mDataSetHasChangedAfterLayout = true;
markKnownViewsInvalid();
}
- 讓
mDispatchItemsChangedEvent
和dispatchItemsChanged
進行或運算,把結果賦值給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方法就看完了,主要工作還是清理工作另外就是啓動重啓佈局計算,好了,下一篇文章就開始看佈局的過程