本文打算對 RecyclerView 作一個詳細完整的、重點突出的分析與總結,由於 RecycelrView 源碼很長(光 RecyclerView 文件自己就有 13000+ 行),所以文章也會很長,但一通分析下來後會發現,RecyclerView 雖然是 ListView 的增強版,除了在使用方法上相似以外,關鍵源碼上也是很是相似的。android
RecyclerView 的使用能夠參考大神的文章:數組
Android RecyclerView 使用徹底解析 體驗藝術般的控件緩存
RecyclerView 和 ListView 使用對比分析bash
本文采用「自頂向下」的源碼分析法——即把相關的代碼按照調用關係自頂向下排列,同一個類的方法放在一塊兒,關鍵的代碼使用註釋標註。廢話很少說,下面從 RecyclerView 的繪製流程開始分析。app
先從 onMeasure 方法看起,關鍵的地方都用註釋標出來了:less
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
Adapter mAdapter;
@VisibleForTesting LayoutManager mLayout;
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) { // 看這裏就好了
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
// 關鍵步驟 1
// 默認爲 start
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// 關鍵步驟 2
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
if (mLayout.shouldMeasureTwice()) {
...
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
...
} else if (mState.mRunPredictiveAnimations) {
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
return;
}
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
...
}
}
void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
// than creating another method since this is internal.
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(),
ViewCompat.getMinimumWidth(this));
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(),
ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height);
}
public abstract static class LayoutManager {
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
}
}
複製代碼
mLayout.isAutoMeasureEnabled() 的返回值默認爲 false,但 LinearLayoutManager 和 StaggeredGridLayoutManager 重寫了這個方法,且默認爲 true:ide
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
@Override
public boolean isAutoMeasureEnabled() {
return true;
}
}
複製代碼
public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements
RecyclerView.SmoothScroller.ScrollVectorProvider {
public static final int GAP_HANDLING_NONE = 0;
public static final int GAP_HANDLING_LAZY = 1;
public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
public void setGapStrategy(int gapStrategy) {
...
mGapStrategy = gapStrategy;
requestLayout();
}
@Override
public boolean isAutoMeasureEnabled() {
return mGapStrategy != GAP_HANDLING_NONE;
}
}
複製代碼
而 GridLayoutManager 繼承自 LinearLayoutManager,即 Android 提供的 LayoutManager 的三個子類,都會走 if 分支裏的代碼,裏面最重要的兩個調用是 dispatchLayoutStep1 和 dispatchLayoutStep2,在開始分析這兩個方法以前,先看一下 onLayout 方法:oop
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
dispatchLayout();
mFirstLayoutComplete = true;
}
/**
* Wrapper around layoutChildren() that handles animating changes caused by layout.
*/
void dispatchLayout() {
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
// onMeasure 已經執行了 step1 和 step2,正常狀況下不會走這個分支
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// adpter 更新了,或者 width 或 height 改變了,從新走一遍 step2
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// 設置 MeasureSpec 爲 EXACTLY
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
// 關鍵步驟 3
dispatchLayoutStep3();
}
public abstract static class LayoutManager {
void setExactMeasureSpecsFrom(RecyclerView recyclerView) {
setMeasureSpecs(
MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)
);
}
}
}
複製代碼
能夠發現,RecyclerView 的 layout 流程分爲三步,關鍵調用分別爲 dispatchLayoutStep一、dispatchLayoutStep二、dispatchLayoutStep3,其中 onMeasure 方法執行前兩部,onLayout 方法執行最後一步,下面開始分析這三個方法。源碼分析
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
/**
* The first step of a layout where we;
* - process adapter updates
* - decide which animation should run
* - save information about current views
* - If necessary, run predictive layout and save its information
*/
private void dispatchLayoutStep1() {
...
processAdapterUpdatesAndSetAnimationFlags();
if (mState.mRunSimpleAnimations) {
// Step 0: 找出全部未刪除的 item 的位置, 準備執行預佈局
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(...);
mViewInfoStore.addToPreLayout(holder, animationInfo);
...
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: 運行預佈局
// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// 藉助 LayoutManager 完成 layout 流程
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
...
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(...);
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass. clearOldPositions(); } else { clearOldPositions(); } mState.mLayoutStep = State.STEP_LAYOUT; } private void processAdapterUpdatesAndSetAnimationFlags() { // 默認爲 false,第一次 measure 時不會執行 if (mDataSetHasChangedAfterLayout) { mAdapterHelper.reset(); if (mDispatchItemsChangedEvent) { mLayout.onItemsChanged(this); } } // 兩個分支裏的代碼最後調用的都是 AdapterHelper 內部的接口 Callback 的方法 // Callback 的實現能夠查看 RecyclerView 的 initAdapterManager 方法 // 最後可能會調用 requestLayout 走一遍繪製流程以實現動畫的效果 if (predictiveItemAnimationsEnabled()) { mAdapterHelper.preProcess(); } else { mAdapterHelper.consumeUpdatesInOnePass(); } mState.mRunSimpleAnimations = mFirstLayoutComplete && ...; mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations && animationTypeSupported && !mDataSetHasChangedAfterLayout && predictiveItemAnimationsEnabled(); } static ViewHolder getChildViewHolderInt(View child) { if (child == null) { return null; } return ((LayoutParams) child.getLayoutParams()).mViewHolder; } } 複製代碼
根據 dispatchLayoutStep1 的註釋及代碼,能夠得知這個方法主要負責:佈局
而若是是第一次執行 measure 流程,adapter 正常來講是沒有能夠更新的元素的,即此時 dispatchLayoutStep1 的做用主要是計算並保存子 View 和動畫的相關信息。
預佈局是否執行涉及的變量比較多,先忽略。如今繼續看 dispatchLayoutStep2:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
/**
* The second layout step where we do the actual layout of the views for the final state.
* This step might be run multiple times if necessary (e.g. measure).
*/
private void dispatchLayoutStep2() {
...
// 藉助 LayoutManager 完成實際上的佈局
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
...
}
}
複製代碼
能夠看到,相比 dispatchLayoutStep1,dispatchLayoutStep2 簡單得多,做用是執行真正的佈局,調用的方法是 mLayout.onLayoutChildren,這個方法在 LayoutManager 中是一個空實現,它的三個子類 LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager 都有本身的實現。下面以 LinearLayoutManager 爲例(關鍵部分,好比緩存機制,三個 LayoutManager 都是同樣的):
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
}
...
// 尋找子 View 的錨點座標及位置
final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION || mPendingSavedState != null) {
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
} else if (...) {
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION
&& mPendingScrollPositionOffset != INVALID_OFFSET) {
// if the child is visible and we are going to move it around, we should layout
// extra items in the opposite direction to make sure new items animate nicely
// instead of just fading in
...
}
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
// 把子 View detach 掉並緩存到 Recycler 裏面
detachAndScrapAttachedViews(recycler);
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
// 填充子 View
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// fill towards end
...
// fill towards start
...
}
...
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
mOrientationHelper.onLayoutComplete();
} else {
mAnchorInfo.reset();
}
...
}
}
複製代碼
結合註釋及代碼,能夠知道 onLayoutChildren 的做用是:尋找到子 View 的錨點座標及錨點位置後加載子 View。錨點座標及位置的計算方法忽略,下面重點關注 detachAndScrapAttachedViews 和 fill 這兩個方法。
這個方法在第一次 measure 的時候不會產生效果,由於此時 RecyclerView 尚未子 View。而在第二第三次 layout 時,它會把子 View 從 RecyclerView 中 remove 或 detach ,並緩存子 View,以便以後從新 add 回來或 attach 回來,避免重複加載相同的子 View——這點是 ListView 幾乎是一致的:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
public abstract static class LayoutManager {
public void detachAndScrapAttachedViews(Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// 移除子 View 並使用 mCacheVies 或 RecycledViewPool 緩存
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
// detach 子 View 並使用 mChangedScarp 或 mAttachedScarp 緩存
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
// add / remove 和 attach/ detach 都是經過 mChildHelper 完成的
// 以後會調用 ChildHelper 內部的接口 Callback 的方法,Callback 的實現能夠查看 RecyclerView 的 initChildrenHelper
// 最終仍是經過 ViewGroup 的 add / remove 和 attach / detach 實現的
public void removeViewAt(int index) {
final View child = getChildAt(index);
if (child != null) {
mChildHelper.removeViewAt(index);
}
}
public void detachViewAt(int index) {
detachViewInternal(index, getChildAt(index));
}
private void detachViewInternal(int index, View view) {
mChildHelper.detachViewFromParent(index);
}
}
/**
* A Recycler is responsible for managing scrapped or detached item views for reuse.
*
* <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
* that has been marked for removal or reuse.</p>
*
* <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
* an adapter's data set representing the data at a given position or item ID. * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. * If not, the view can be quickly reused by the LayoutManager with no further work. * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} * may be repositioned by a LayoutManager without remeasurement.</p> */ public final class Recycler { final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); ArrayList<ViewHolder> mChangedScrap = null; final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); private final List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); static final int DEFAULT_CACHE_SIZE = 2; private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; // mCachedVies 最多放 2 個,這個容量限制會在 mCachedView 調用 add 方法時起做用 int mViewCacheMax = DEFAULT_CACHE_SIZE; RecycledViewPool mRecyclerPool; private ViewCacheExtension mViewCacheExtension; void recycleViewHolderInternal(ViewHolder holder) { ... boolean cached = false; boolean recycled = false; if (forceRecycle || holder.isRecyclable()) { // 若是符合條件,就放到 mCachedViews 裏面 if (mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { // Retire oldest cached view int cachedViewSize = mCachedViews.size(); // 超過緩存容量限制,把最老的一個 View 放進 RecycledViewPool 裏面 if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { recycleCachedViewAt(0); cachedViewSize--; } ... // 使用 mCachedViews 緩存子 View mCachedViews.add(targetCacheIndex, holder); cached = true; } // 不然放到 RecycledViewPool 裏面 if (!cached) { addViewHolderToRecycledViewPool(holder, true); recycled = true; } } else { ... } // even if the holder is not removed, we still call this method so that it is removed // from view holder lists. mViewInfoStore.removeViewHolder(holder); } // 把一個 View 從 mCachedViews 裏刪除,並 put 進 RecycledViewPool 裏面 void recycleCachedViewAt(int cachedViewIndex) { ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); addViewHolderToRecycledViewPool(viewHolder, true); mCachedViews.remove(cachedViewIndex); } void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) { ... getRecycledViewPool().putRecycledView(holder); } /** * Mark an attached view as scrap. */ void scrapView(View view) { final ViewHolder holder = getChildViewHolderInt(view); if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { mAttachedScrap.add(holder); } else { mChangedScrap.add(holder); } } } /** * RecycledViewPool lets you share Views between multiple RecyclerViews. */ public static class RecycledViewPool { private static final int DEFAULT_MAX_SCRAP = 5; static class ScrapData { // mScrapHeap 最多放 5 個 final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } // 每種類型對應一個 ScrapData,即每種類型最多緩存 5 個 View SparseArray<ScrapData> mScrap = new SparseArray<>(); private int mAttachCount = 0; public void putRecycledView(ViewHolder scrap) { final int viewType = scrap.getItemViewType(); final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap; scrapHeap.add(scrap); } private ScrapData getScrapDataForType(int viewType) { ScrapData scrapData = mScrap.get(viewType); if (scrapData == null) { scrapData = new ScrapData(); mScrap.put(viewType, scrapData); } return scrapData; } } } 複製代碼
能夠看到,detachAndScrapAttachedViews 這個不起眼的方法已經觸及 RecyclerView 的核心了,它的做用有能夠總結爲一句話:remove / detach 全部子 View,並緩存它們。其中,若是根據 ViewHolder 狀態得知子 View 能夠被移除,則移除子 View,並緩存到 Recycler 的 mCachedViews 數組裏面,mCachedViews 默認只緩存 2 個,若是 mCachedViews 容量超出限制,則把 mCachedViews 數組裏最老的一個 item 移除,放到 RecycledViewPool 的 mScrapHeap 數組裏。若是子 View 沒法被移除,那麼 detach 掉,並判斷子 View 是否發生了改變,改變了的子 View 緩存到 Recycler 的 mChangedScrap 數組,不然緩存到 Recycler 的 mAttachedScrap 數組。
Recycler 和 RecycledViewPool 的區別是,一個 Recycler 對應一個 RecyclerView;一個 RecycledViewPool 能夠對應多個 RecyclerView。即 RecycledViewPool 裏面的 View 能夠提供給多個 RecyclerView 重用。
Scrap View 表明移除了的能夠重用的視圖。若是即將要重用的 View 標記爲 dirty,則須要 Adapter 從新綁定,不然 LayoutManager 能夠直接使用。
detachAndScrapAttachedViews 就分析到這裏,接下來繼續分析 onLayoutChildren。 onLayoutChildren 方法中下一個須要關注的調用是 fill。
fill 方法特別關鍵——若是說 detachAndScrapAttachedViews 觸及了 RecyclerView 的核心,那麼 fill 方法就是真正的核心。它是從 Adapter 中加載子 View、從回收池中複用 View、並顯示到 RecyclerView 上的關鍵調用,源碼做者稱它爲 magic method:
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
/**
* The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
* independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
* and with little change, can be made publicly available as a helper class.
*/
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
// 這個 while 循環是最關鍵的部分,它代表了 RecyclerView 和 ListView 同樣,只會加載一個手機屏幕能顯示的子 View
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// 加載子 View
layoutChunk(recycler, state, layoutState, layoutChunkResult);
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
// 若是當前正在滾動屏幕
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 把移出屏幕的 View 緩存到 mCachedViews 裏面
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
return start - layoutState.mAvailable;
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 從 Adapter 或回收池中獲取一個 View
View view = layoutState.next(recycler);
if (view == null) {
result.mFinished = true;
return;
}
LayoutParams params = (LayoutParams) view.getLayoutParams();
// 添加子 View 到 RecyclerView 上面
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
// 執行子 View 的 measure 流程
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
...
// We calculate everything with View's bounding box (which includes decor and margins) // To calculate correct layout position, we subtract margins. layoutDecoratedWithMargins(view, left, top, right, bottom); result.mFocusable = view.hasFocusable(); } static class LayoutState { List<RecyclerView.ViewHolder> mScrapList = null; /** * Gets the view for the next element that we should layout. * Also updates current item index to the next item, based on {@link #mItemDirection} */ View next(RecyclerView.Recycler recycler) { if (mScrapList != null) { // 只在特定時候纔不爲空,省略 return nextViewFromScrapList(); } // 從 Adapter 或回收池中獲取一個 View final View view = recycler.getViewForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view; } } } 複製代碼
能夠看到,fill 方法的做用就是循環獲取一個個子 View,並加載到屏幕上,直到屏幕沒有剩餘空間或多餘的子 View,具體執行的方法是 layoutChunk,它首先調用 getViewForPosition 嘗試獲取一個子 View,接着調用 addView 方法把它加載到 RecyclerView 中,最後再執行子 View 的 measure 流程。同時,它會根據 LayoutState 判斷 RecyclerView 是否正在滾動中,若是是,則會把移出屏幕以外的子 View 緩存以便以後重用。下面看看它是如何實現的,先從 getViewForPosition 開始分析:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
public final class Recycler {
/**
* Obtain a view initialized for the given position.
*
* This method should be used by {@link LayoutManager} implementations to obtain
* views to represent data from an {@link Adapter}.
* <p>
* The Recycler may reuse a scrap or detached view from a shared pool if one is
* available for the correct view type. If the adapter has not indicated that the
* data at the given position has changed, the Recycler will attempt to hand back
* a scrap view that was previously initialized for that data without rebinding.
*/
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
/**
* Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
* cache, the RecycledViewPool, or creating it directly.
*/
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) 若是是預佈局,則嘗試從 mChangedScrap 中獲取
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) 根據 position 依次從 mAttachedScrap, mCachedViews 中獲取
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used // 這個 View 會被重用,從緩存池中移除它 if (holder.isScrap()) { removeDetachedView(holder.itemView, false); holder.unScrap(); } if (!dryRun) { recycleViewHolderInternal(holder); } holder = null; } else { fromScrapOrHiddenOrCache = true; } } } if (holder == null) { final int type = mAdapter.getItemViewType(offsetPosition); // 2) 若是上一步沒有獲取到,則根據 id 依次從 mAttachedScrap, mCachedViews 中獲取 // 2) Find from scrap/cache via stable ids, if exists if (mAdapter.hasStableIds()) { // 和 getScrapOrHiddenOrCachedHolderForPosition 相似,省略具體代碼 holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrapOrHiddenOrCache = true; } } if (holder == null && mViewCacheExtension != null) { // 從 mViewCacheExtension 中獲取 // We are NOT sending the offsetPosition because LayoutManager does not know it. final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type); if (view != null) { holder = getChildViewHolder(view); ... } } if (holder == null) { // fallback to pool // 尚未獲取到,則嘗試從 RecycledViewPool 中獲取 holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { holder.resetInternal(); } } if (holder == null) { // 沒有能夠複用的 View,則回調 mAdapter 的 onCreateViewHolder 方法加載一個子 View holder = mAdapter.createViewHolder(RecyclerView.this, type); ... } } boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // 若是是預佈局,則設置子 View 的 position 信息便可 // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { // 不然回調 Adapter 的 onBindViewHolder 方法,爲子 View 綁定數據 final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound; return holder; } ViewHolder getChangedScrapViewForPosition(int position) { ... // find by position for (int i = 0; i < changedScrapSize; i++) { // 從 mChangedScrap 中獲取緩存的 View final ViewHolder holder = mChangedScrap.get(i); if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); return holder; } } // find by id if (mAdapter.hasStableIds()) { ... } return null; } /** * Returns a view for the position either from attach scrap, hidden children, or cache. * * @param dryRun Does a dry run, finds the ViewHolder but does not remove */ ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) { final int scrapCount = mAttachedScrap.size(); // Try first for an exact, non-invalid match from scrap. for (int i = 0; i < scrapCount; i++) { final ViewHolder holder = mAttachedScrap.get(i); if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); return holder; } } if (!dryRun) { // 做用是把以前隱藏的 View 從新拿出來,這裏不把它算做緩存機制的一部分 View view = mChildHelper.findHiddenNonRemovedView(position); if (view != null) { // This View is good to be used. We just need to unhide, detach and move to the // scrap list. final ViewHolder vh = getChildViewHolderInt(view); mChildHelper.unhide(view); ... mChildHelper.detachViewFromParent(layoutIndex); scrapView(view); return vh; } } // Search in our first-level recycled view cache. final int cacheSize = mCachedViews.size(); for (int i = 0; i < cacheSize; i++) { final ViewHolder holder = mCachedViews.get(i); // invalid view holders may be in cache if adapter has stable ids as they can be // retrieved via getScrapOrCachedViewForId if (!holder.isInvalid() && holder.getLayoutPosition() == position) { // 從緩存池中移除,getViewForPosition 中傳過來的 dryRun 值爲 false if (!dryRun) { mCachedViews.remove(i); } return holder; } } return null; } private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition, int position, long deadlineNs) { ... mAdapter.bindViewHolder(holder, offsetPosition); ... return true; } } public abstract static class Adapter<VH extends ViewHolder> { private final AdapterDataObservable mObservable = new AdapterDataObservable(); public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) { ... // 從 xml 資源中加載子 View 並建立 ViewHolder,這是咱們重寫 Adapter 時的關鍵方法之一 final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; return holder; ... } public final void bindViewHolder(@NonNull VH holder, int position) { holder.mPosition = position; if (hasStableIds()) { holder.mItemId = getItemId(position); } // 綁定數據到 ViewHolder,這是咱們重寫 Adapter 時的關鍵方法之一 onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); ... } } } 複製代碼
能夠看到,getViewForPosition 最終是按照一個固定的順序從 Recycler 或 RecycledViewPool 中一個個嘗試獲取的,流程爲:mChangedScrap -> mAttachedScrap -> mCachedViews -> mViewCacheExtension -> RecycledViewPool,若是沒有可用的緩存,則會調用 Adapter 的 onCreateViewHolder 方法從 xml 資源中加載子 View。
成功獲取到一個 itemView 以後,若是當前屬於預佈局階段,則設置 itemView 的位置信息便可;不然調用 Adapter 的 onBindViewHolder 綁定數據到 itemView 中。接着就能夠調用 addView 方法把 itemView 加載到 RecyclerView 中了:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
public abstract static class LayoutManager {
public void addView(View child) {
addView(child, -1);
}
public void addView(View child, int index) {
addViewInt(child, index, false);
}
public void addDisappearingView(View child) {
addDisappearingView(child, -1);
}
public void addDisappearingView(View child, int index) {
addViewInt(child, index, true);
}
private void addViewInt(View child, int index, boolean disappearing) {
final ViewHolder holder = getChildViewHolderInt(child);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (holder.wasReturnedFromScrap() || holder.isScrap()) {
// 從緩存池中從新拿出來的子 View, attach 便可
mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
} else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
// 仍然在屏幕上顯示的子 View,移動到新的位置便可
if (currentIndex != index) {
mRecyclerView.mLayout.moveView(currentIndex, index);
}
} else {
// 新的子 View,添加到 RecyclerView 上
mChildHelper.addView(child, index, false);
}
if (lp.mPendingInvalidate) {
holder.itemView.invalidate();
lp.mPendingInvalidate = false;
}
}
/**
* Moves a View from one position to another.
*/
public void moveView(int fromIndex, int toIndex) {
View view = getChildAt(fromIndex);
detachViewAt(fromIndex);
attachView(view, toIndex);
}
}
}
複製代碼
前面說過,若是當前正在滾動屏幕,那麼 fill 方法還會調用 recycleByLayoutState 方法緩存移出屏幕以外的子 View:
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
// 根據滑動的距離計算哪些 View 須要被移除
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
} else {
recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
}
}
private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
final int childCount = getChildCount();
final int limit = mOrientationHelper.getEnd() - dt;
if (mShouldReverseLayout) {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedStart(child) < limit
|| mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
// 緩存
// stop here
recycleChildren(recycler, 0, i);
return;
}
}
} else {
...
}
}
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
...
// 移除並緩存
if (endIndex > startIndex) {
for (int i = endIndex - 1; i >= startIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
} else {
for (int i = startIndex; i > endIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
}
}
}
複製代碼
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
public abstract static class LayoutManager {
public void removeAndRecycleViewAt(int index, Recycler recycler) {
final View view = getChildAt(index);
// 從 RecyclerView 中移除屏幕以外的子 View
removeViewAt(index);
// 移除後緩存
recycler.recycleView(view);
}
public void removeViewAt(int index) {
final View child = getChildAt(index);
if (child != null) {
mChildHelper.removeViewAt(index);
}
}
}
public final class Recycler {
public void recycleView(View view) {
// This public recycle method tries to make view recycle-able since layout manager
// intended to recycle this view (e.g. even if it is in scrap or change cache)
ViewHolder holder = getChildViewHolderInt(view);
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
// 把 ViewHolder 從 mChangedScrap 或 mAttachedScrap 中移除
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
// 這個方法以前分析過,用於緩存 ViewHolder 到 mCachedViews 中
recycleViewHolderInternal(holder);
}
void unscrapView(ViewHolder holder) {
// 從 mChangedScrap 或 mAttachedScrap 中移除
if (holder.mInChangeScrap) {
mChangedScrap.remove(holder);
} else {
mAttachedScrap.remove(holder);
}
holder.mScrapContainer = null;
holder.mInChangeScrap = false;
holder.clearReturnedFromScrapFlag();
}
}
public abstract static class ViewHolder {
private Recycler mScrapContainer = null;
void unScrap() {
mScrapContainer.unscrapView(this);
}
}
}
複製代碼
能夠看到,fill 最終會把移除屏幕以外的子 View 緩存到 mCahcedViews 裏面,若是這個 View 已經緩存到了 mChangedScrap 或 mAttachedScrap 中,那麼還會把它移除出來,轉移到 mCahcedViews 裏面。
從上面的分析中能夠看出,RecyclerView 的工做機制和 ListView 是很像的:
區別在於:
RecyclerView 的緩存機制在這裏就基本分析完成了,下面繼續看 dispatchLayoutStep3。
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
/**
* The final step of the layout where we save the information about views for animations,
* trigger animations and do any necessary cleanup.
*/
private void dispatchLayoutStep3() {
...
// 重置 LayoutState
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now, and process change animations.
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore()) {
continue;
}
long key = getChangedHolderKey(holder);
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
// run a change animation
if (oldDisappearing && oldChangeViewHolder == holder) {
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
oldChangeViewHolder);
// we add and remove so that any post info is merged.
mViewInfoStore.addToPostLayout(holder, animationInfo);
ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
if (preInfo == null) {
handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
} else {
// 執行動畫
animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
oldDisappearing, newDisappearing);
}
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// 執行 mViewInfoProcessCallback 裏的動畫
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
// 把 mAttachedScrap 裏緩存的 View 移動到 mCachedViews 裏面
mLayout.removeAndRecycleScrapInt(mRecycler);
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
// 把 mCachedViews 多餘的 View 移動到 RecycledViewPool 裏面
mRecycler.updateViewCacheSize();
}
mLayout.onLayoutCompleted(mState);
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
mViewInfoStore.clear();
if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
dispatchOnScrolled(0, 0);
}
recoverFocusFromState();
resetFocusInfo();
}
private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
boolean oldHolderDisappearing, boolean newHolderDisappearing) {
...
if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
postAnimationRunner();
}
}
/**
* The callback to convert view info diffs into animations.
*/
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
@Override
public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
@Nullable ItemHolderInfo postInfo) {
mRecycler.unscrapView(viewHolder);
animateDisappearance(viewHolder, info, postInfo);
}
@Override
public void processAppeared(ViewHolder viewHolder,
ItemHolderInfo preInfo, ItemHolderInfo info) {
animateAppearance(viewHolder, preInfo, info);
}
// 各類動畫最終都調用了 postAnimationRunner 執行
...
};
void animateDisappearance(@NonNull ViewHolder holder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
/**
* Post a runnable to the next frame to run pending item animations. Only the first such
* request will be posted, governed by the mPostedAnimatorRunner flag.
*/
void postAnimationRunner() {
if (!mPostedAnimatorRunner && mIsAttached) {
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
// DefaultItemAnimator 是 RecyclerView 默認的動畫執行者
ItemAnimator mItemAnimator = new DefaultItemAnimator();
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
if (mItemAnimator != null) {
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
};
public abstract static class LayoutManager {
/**
* Recycles the scrapped views.
*/
void removeAndRecycleScrapInt(Recycler recycler) {
final int scrapCount = recycler.getScrapCount();
// Loop backward, recycler might be changed by removeDetachedView()
for (int i = scrapCount - 1; i >= 0; i--) {
final View scrap = recycler.getScrapViewAt(i);
final ViewHolder vh = getChildViewHolderInt(scrap);
if (vh.isTmpDetached()) {
mRecyclerView.removeDetachedView(scrap, false);
}
if (mRecyclerView.mItemAnimator != null) {
mRecyclerView.mItemAnimator.endAnimation(vh);
}
// 放入 mCachedViews 裏面
recycler.quickRecycleScrapView(scrap);
}
recycler.clearScrap();
if (scrapCount > 0) {
mRecyclerView.invalidate();
}
}
int getScrapCount() {
return mAttachedScrap.size();
}
View getScrapViewAt(int index) {
return mAttachedScrap.get(index).itemView;
}
}
}
複製代碼
能夠看到,dispatchLayoutStep3 的主要做用就是執行動畫和作一些清理工做,默認的動畫執行者是 DefaultItemAnimator,清理工做包括把 mAttachedScrap 裏緩存的 View 移動到 mCachedViews 裏面,若是 mCachedViews 容量超出限制,則移動一部分到 RecycledViewPool 裏,同時清理 mChangedScrap 的全部元素。
通過上面的分析,能夠基本確定 RecyclerView 在滑動加載更多數據時,會調用 fill 方法使用 mCachedViews 進行子 View 的緩存和複用,下面來驗證一下:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
@Override
public boolean onTouchEvent(MotionEvent e) {
...
switch (action) {
...
case MotionEvent.ACTION_MOVE: {
...
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
...
}
return true;
}
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0, unconsumedY = 0;
int consumedX = 0, consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
...
// 滾動的事件處理由 LayoutManager 完成
if (x != 0) {
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
}
if (y != 0) {
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
}
if (!mItemDecorations.isEmpty()) {
invalidate();
}
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
TYPE_TOUCH)) {
...
} else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
...
}
return consumedX != 0 || consumedY != 0;
}
// 若是 Adapter 更新了,則從新走一遍 layout 流程
void consumePendingUpdateOperations() {
if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
dispatchLayout();
return;
}
if (!mAdapterHelper.hasPendingUpdates()) {
return;
}
// if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
// of the visible items is affected and if not, just ignore the change.
if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
.hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
| AdapterHelper.UpdateOp.MOVE)) {
if (!mLayoutWasDefered) {
if (hasUpdatedView()) {
dispatchLayout();
} else {
// no need to layout, clean state
mAdapterHelper.consumePostponedUpdates();
}
}
} else if (mAdapterHelper.hasPendingUpdates()) {
dispatchLayout();
}
}
}
複製代碼
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return 0;
}
return scrollBy(dx, recycler, state);
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) {
return 0;
}
mLayoutState.mRecycle = true;
...
// fill 在上面分析過,即循環獲取子 View 直到屏幕填充完畢,或子 View 消耗完畢
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
...
return scrolled;
}
}
複製代碼
能夠看到,就像上面的推測的那樣,RecyclerView 在滑動過程當中會調用 fill 方法,使用 mCachedViews 進行子 View 的緩存和複用,若是 Adapter 更新了,則會從新走了一遍 layout 的流程。
上面能夠說已經把 RecyclerView最關鍵的部分都分析完成了,但還有一些問題是沒有解決的,即 RecyclerView 是如何得知 Adapter 更新的?下面開始分析這個問題。
從 RecyclerView 的 setAdapter 方法開始看起:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
Adapter mAdapter;
AdapterHelper mAdapterHelper;
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
requestLayout();
}
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
...
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
...
}
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
mDispatchItemsChangedEvent |= dispatchItemsChanged;
// 標記 DataSet 改變了,以後會調用 dispatchLayout 走一遍 layout 流程
mDataSetHasChangedAfterLayout = true;
markKnownViewsInvalid();
}
public abstract static class Adapter<VH extends ViewHolder> {
// 使用了觀察者模式
private final AdapterDataObservable mObservable = new AdapterDataObservable();
@NonNull
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
public abstract void onBindViewHolder(@NonNull VH holder, int position);
...
// 註冊觀察者
public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
mObservable.registerObserver(observer);
}
public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) {
mObservable.unregisterObserver(observer);
}
// 通知觀察者,數據發生了改變
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
public final void notifyItemInserted(int position) {
mObservable.notifyItemRangeInserted(position, 1);
}
...
}
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
// 通知全部觀察者
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
}
}
...
}
private class RecyclerViewDataObserver extends AdapterDataObserver {
RecyclerViewDataObserver() {
}
@Override
public void onChanged() {
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
// 若是設置了 mHasFixedSize,那麼可能會省略一些工做
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
// 不然須要從新走一遍 View 的三大流程
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
final Runnable mUpdateChildViewsRunnable = new Runnable() {
@Override
public void run() {
if (!mFirstLayoutComplete || isLayoutRequested()) {
// a layout request will happen, we should not do layout here.
return;
}
if (!mIsAttached) {
requestLayout();
// if we are not attached yet, mark us as requiring layout and skip
return;
}
if (mLayoutFrozen) {
mLayoutWasDefered = true;
return; //we'll process updates when ice age ends. } consumePendingUpdateOperations(); } }; void consumePendingUpdateOperations() { // 從新執行一遍 dispatchLayoutStep一、dispatchLayoutStep二、dispatchLayoutStep3 if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) { dispatchLayout(); return; } if (!mAdapterHelper.hasPendingUpdates()) { return; } // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any // of the visible items is affected and if not, just ignore the change. if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE | AdapterHelper.UpdateOp.MOVE)) { // 局部更新 // 最終調用了 AdapterHelper 的 Callback 方法,Callback 的實現能夠查看 RecyclerView 的 initAdapterManager 方法 mAdapterHelper.preProcess(); if (!mLayoutWasDefered) { if (hasUpdatedView()) { dispatchLayout(); } else { // no need to layout, clean state mAdapterHelper.consumePostponedUpdates(); } } } else if (mAdapterHelper.hasPendingUpdates()) { dispatchLayout(); } } } 複製代碼
根據上面的分析,能夠知道:
RecyclerView 的 measure 和 layout 分爲三步:
對比 RecyclerView 和 ListView:
區別在於: