天天看點

RecyclerView分析

概要

RecyclerView能夠在有限的視圖中展示大量的資料,RecyclerView隻會和ViewHolder進行接觸,而Adapter的工作就是将Data轉換為RecyclerView認識的ViewHolder,是以RecyclerView就間接地認識了Data。而LayoutManager負責完成布局的具體工作,而Recycler負責對 View進行管理,而ItemAnimator負責與View相關的動畫;

RecyclerView.onMeasure()

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
  //mLayout指的是LayoutManager,如果為空,則走RecyclerView的Measure過程(defaultOnMeasure)
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
  //對于LinearLayoutManager來說mAutoMeasure會被預設設定為ture;
  //而GridLayoutManager是繼承自LinearLayoutManager,是以預設也是 ture;
    if (mLayout.mAutoMeasure) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
      //這裡最終還是走RecyclerView的Measure過程(defaultOnMeasure)
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
      //如果寬高都是EXACTLY(确定的)或者沒設定 Adapter,則結束測量
        if (skipMeasure || mAdapter == null) {
            return;
        }

      // RecyclerView.State ,這個類封裝了目前RecyclerView的諸多資訊,包括焦點,滾動,資源 id.....;
      //State的一個變量mLayoutStep表示了RecyclerView目前的布局狀态,包括STEP_START、STEP_LAYOUT 、 STEP_ANIMATIONS三個;

      //RecyclerView的布局過程也分為三步,step1負責記錄狀态,step2負責布局,step3則與step1進行比較,根據變化來觸發動畫。

      //第一步:設定一些 Viewr的基本資訊,如果有動畫周遊目前所有子 View,拿到 ViewHolder與ItemHolderInfo(animationInfo),
      //然後将ItemHolderInfo資訊指派給InfoRecord的preInfo變量。
      //最後标記InfoRecord的flags為FLAG_PRE,并将ViewHolder、InfoRecord二者放入mViewInfoStore的mLayoutHolderMap中

       //測量第一步,移步dispatchLayoutStep1()方法
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }

        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;

       //測量第二步,移步dispatchLayoutStep2()方法
        dispatchLayoutStep2();
    } else {
     //********************************省略*********************************
    }
}
           

RecyclerView.dispatchLayoutStep1()

private void dispatchLayoutStep1() {
        mState.assertLayoutStep(State.STEP_START);
        mState.mIsMeasuring = false;
        //通過加減計數的方式,判斷是否需要忽略來自child 的 requestLayout的調用
        eatRequestLayout();

        //mViewInfoStore主要存儲了些跟動畫有關的資訊
        mViewInfoStore.clear();

        //通過計數來标記目前是否處于 Layout或 Scroll 狀态,處于這兩個狀态時,一些動作是不允許的(比如更新  adapter 這個動作)
        //這裡是加
        onEnterLayoutOrScroll();

        //決定是否跑動畫以及跑何種動畫
        processAdapterUpdatesAndSetAnimationFlags();

        //找到目前焦點在那個Child View上
        saveFocusInfo();

        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        mState.mInPreLayout = mState.mRunPredictiveAnimations;
        mState.mItemCount = mAdapter.getItemCount();
        //找到目前螢幕中完全顯示的Child View 的最大最小位置,存入mMinMaxLayoutPositions中
        //ps:以參數作為傳回值也是醉了
        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

        if (mState.mRunSimpleAnimations) {
            //這裡會周遊所有item,然後找到所有沒有被移除的 item
            int count = mChildHelper.getChildCount();
            for (int i = ; i < count; ++i) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                    continue;
                }
                //ItemHolderInfo記錄着 item 的邊界坐标資訊(left、top、right、bottom)這些資訊用來跑動畫
                final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(mState, holder,
                                ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),holder.getUnmodifiedPayloads());
                 //将ViewHolder以及與ItemHolderInfo一一對應,記錄下來             
                mViewInfoStore.addToPreLayout(holder, animationInfo);
                if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()&& !holder.shouldIgnore() && !holder.isInvalid()) {
                    long key = getChangedHolderKey(holder);
                    mViewInfoStore.addToOldChangeHolders(key, holder);
                }
            }
        }
        if (mState.mRunPredictiveAnimations) {
             //這裡會周遊所有item,然後進行預布局

            // 儲存 item 的舊位置,然後LayoutManager可以進行布局映射
            saveOldPositions();
            final boolean didStructureChange = mState.mStructureChanged;
            mState.mStructureChanged = false;
            // temporarily disable flag because we are asking for previous layout
            mLayout.onLayoutChildren(mRecycler, mState);
            mState.mStructureChanged = didStructureChange;

            for (int i = ; i < mChildHelper.getChildCount(); ++i) {
                final View child = mChildHelper.getChildAt(i);
                final ViewHolder viewHolder = getChildViewHolderInt(child);
                if (viewHolder.shouldIgnore()) {
                    continue;
                }
                if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                    int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                    boolean wasHidden = viewHolder
                            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    if (!wasHidden) {
                        flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    }
                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                            mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                    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();
        }

         //通過計數來标記目前是否處于 Layout或 Scroll 狀态,處于這兩個狀态時,一些動作是不允許的(比如更新  adapter 這個動作)
        //這裡是減,與上面對應
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
        //更新 Layout 狀态,進入下一步 Layout
        mState.mLayoutStep = State.STEP_LAYOUT;
    }
           

總結下dispatchLayoutStep1()做的事情:

1. 處理 adapter 更新;

2. 決定是否需要跑動畫;

3. 進行預布局;

接着看onMeasure()中的dispatchLayoutStep2()

RecyclerView.dispatchLayoutStep2()

private void dispatchLayoutStep2() {
   //********************************省略*********************************
   mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
   //重點:以LinearLayoutManager為例
    mLayout.onLayoutChildren(mRecycler, mState);
   //********************************省略*********************************
    mState.mLayoutStep = State.STEP_ANIMATIONS;
}
           

layout的第二步主要就是真正的去布局View了,RecyclerView的布局是由LayoutManager負責的,是以第二步的主要工作也都在LayoutManager中,由于每種布局的方式不一樣,這裡我們以常見的LinearLayoutManager為例:

LinearLayoutManage.onLayoutChildren()

@Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //********************************省略*********************************
        mAnchorInfo.reset();
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
        // 尋找anchor
       //首先尋找被focus的child,找到的話以此child作為anchor,否則根據布局的方向尋找最合适的child來作為anchor,
       //如果找到則将child的資訊指派給anchorInfo,其實anchorInfo主要記錄的資訊就是view的實體位置與在adapter中的位置。
       //一般是 child 的第一個或最後一個。
        updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
        //********************************省略*********************************
        if (mAnchorInfo.mLayoutFromEnd) {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
                    LayoutState.ITEM_DIRECTION_HEAD;
        } else {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
                    LayoutState.ITEM_DIRECTION_TAIL;
        }

        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
        detachAndScrapAttachedViews(recycler);
        mLayoutState.mInfinite = mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED;
        mLayoutState.mIsPreLayout = state.isPreLayout();
      //從後往前布局
        if (mAnchorInfo.mLayoutFromEnd) {
            //********************************省略*********************************
            fill(recycler, mLayoutState, state, false);
            //********************************省略*********************************
        } 
      //從前往後布局
      else {
        //********************************省略*********************************

        //無論從上到下還是從下到上布局,都調用的是fill方法,fill中有兩個關鍵的方法:
        //一是layoutChunk(),負責添加add view 到RecyclerView中.
        //layoutChunk擷取View的方法是通過調用RecyclerView.getViewForPosition()來擷取相應的View(取緩存或建立);
        //二是recycleByLayoutState(),負責回收已經逃離出螢幕的View,recycleByLayoutState最終會調用Recycler。
        //recycleViewHolderInternal()對View進行回收;
            fill(recycler, mLayoutState, state, false);

        //********************************省略*********************************
        }
    }
           

dispatchLayoutStep2大緻過程:

  1. 找到anchor點
  2. 根據anchor一直向前布局,直至填充滿anchor點前面的所有區域
  3. 根據anchor一直向後布局,直至填充滿anchor點後面的所有區域
  4. anchor點的尋找是由updateAnchorInfoForLayout函數負責的:

    接着看上面調用的那個fill(…)方法:

LinearLayoutManage.fill( )

int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
        final int start = layoutState.mAvailable;
      //********************************省略*********************************
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > ) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            //對子 View進行布局
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (layoutChunkResult.mFinished) {
                break;
            }
    //********************************省略*********************************
            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < ) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                //對 View進行回收
                recycleByLayoutState(recycler, layoutState);
            }
            if (stopOnFocusable && layoutChunkResult.mFocusable) {
                break;
            }
        }

        return start - layoutState.mAvailable;
    }
           

fill中有兩個關鍵的方法:一是layoutChunk(),負責添加add view 到RecyclerView中。二是recycleByLayoutState(),負責回收已經逃離出螢幕的View。

先來看layoutChunk():

LinearLayoutManage.layoutChunk()

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        //擷取childView,這個與複用緩存有關,回頭再看
        View view = layoutState.next(recycler);
        if (view == null) {
             //********************************省略*********************************
            return;
        }
        LayoutParams params = (LayoutParams) view.getLayoutParams();

        //********************************省略*********************************

        //這時會去測量childView的寬高,它會把 Margin以及ItemDecor考慮進去
        measureChildWithMargins(view, , );
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        //垂直方向布局
        if (mOrientation == VERTICAL) {
        //從右往左的布局,這裡其實是根據布局方向以及ItemDecor計算childView 的四個頂點位置
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } 
        //水準方向布局,類似
        else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        //對childView 進行布局,會調用childView.layout(...)
        layoutDecorated(view, left + params.leftMargin, top + params.topMargin,right - params.rightMargin, bottom - params.bottomMargin);
        //********************************省略*********************************
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.isFocusable();
    }
           

接着看fill中第二個方法recycleByLayoutState:

LinearLayoutManager.recycleByLayoutState()

private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mRecycle) {
            return;
        }
        //RecyclerView向 position=0 滑動, 回收position=N
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        }
        //RecyclerView向 position=N 滑動, 回收position=0
        else {
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    }
           

recycleViewsFromEnd與recycleViewsFromStart兩個方向實作類似,最後都是通過recycleChildren方法來回收 child,以recycleViewsFromEnd為例:

LinearLayoutManager.recycleViewsFromEnd()

private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
        final int childCount = getChildCount();
       //********************************省略*********************************
        final int limit = mOrientationHelper.getEnd() - dt;
        //布局是否翻轉,還記得構造 LinearLayoutManager 傳入的 boolean 值嘛?
        if (mShouldReverseLayout) {
            for (int i = ; i < childCount; i++) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
                    recycleChildren(recycler, , i);
                    return;
                }
            }
        } else {
            for (int i = childCount - ; i >= ; i--) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
                    recycleChildren(recycler, childCount - , i);
                    return;
                }
            }
        }

    }
           

recycleViewsFromEnd判斷了childView是否在 RecyclerView邊界之外了,如果是就調用recycleChildren();

LinearLayoutManager.recycleChildren()

private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        if (startIndex == endIndex) {
            return;
        }

        //  startIndex與endIndex代表着回收區間, 僅僅是回收順序不同 ,取決于mShouldReverseLayout
        if (endIndex > startIndex) {
            for (int i = endIndex - ; i >= startIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        } else {
            for (int i = startIndex; i > endIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        }
    }
           

在回收區間内,對每個 View調用removeAndRecycleViewAt();

LinearLayoutManager.removeAndRecycleViewAt()

public void removeAndRecycleViewAt(int index, Recycler recycler) {
            final View view = getChildAt(index);
            //這裡會從 RecyclerVeiw中移除 childVeiw
            removeViewAt(index);
            //回收childView
            recycler.recycleView(view);
        }
           

看 View 是如何被回收的

Recycler.recycleView()

public void recycleView(View view) {
            ViewHolder holder = getChildViewHolderInt(view);
            //********************************省略*********************************
            recycleViewHolderInternal(holder);
        }
           

直接看recycleViewHolderInternal()的實作:

Recycler.recycleViewHolderInternal()

void recycleViewHolderInternal(ViewHolder holder) {
             //********************************省略*********************************
            final boolean transientStatePreventsRecycling = holder.doesTransientStatePreventRecycling();
            final boolean forceRecycle = mAdapter != null && transientStatePreventsRecycling&& mAdapter.onFailedToRecycleView(holder);
            boolean cached = false;
            boolean recycled = false;

            if (forceRecycle || holder.isRecyclable()) {
                if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE)) {
                 // mCachedViews是用來緩存ViewHolder的一個 list
                    final int cachedViewSize = mCachedViews.size();
                  // mCachedViews的大小是否達到了上限值,mViewCacheMax預設大小是2
                    if (cachedViewSize == mViewCacheMax && cachedViewSize > ) {
                    //達到上限
                        recycleCachedViewAt();
                    }
                    //如果沒有達到上限值,直接将 holder加入 mCachedViews中,上面recycleCachedViewAt()中有個移除操作,這時應該是沒達到上限的
                    if (cachedViewSize < mViewCacheMax) {
                        mCachedViews.add(holder);
                        cached = true;
                    }
                }
            mViewInfoStore.removeViewHolder(holder);
            if (!cached && !recycled && transientStatePreventsRecycling) {
                holder.mOwnerRecyclerView = null;
            }
        }

    }
           

如果mCachedViews這個 List的 size 沒有達到上限(預設2),那麼就很簡單了,直接将要回收的 ViewHolder加入到mCachedViews中就完事了,

下面看看達到上限的情況,達到上限時,調用的是recycleCachedViewAt(),并傳入了參數0:

Recycler.recycleCachedViewAt()

void recycleCachedViewAt(int cachedViewIndex) {
          //剛剛cachedViewIndex傳入的是0,其實就是取出了最早放入mCachedViews中的那個 ViewHolder
            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
            //加入 ViewHolder Pool 中
            addViewHolderToRecycledViewPool(viewHolder);
            //從mCachedViews中移除ViewHolder
            mCachedViews.remove(cachedViewIndex);
        }
           

recycleCachedViewAt()做的就是在mCachedViews數量超過上限時将舊的 ViewHolder 加到 Pool中并從mCachedViews中移除,以保證新的 ViewHoler 能加入到mCachedViews中去。

那麼看看舊的 ViewHolder 是如何加入到 Pool中去的。

Recycler.addViewHolderToRecycledViewPool()

void addViewHolderToRecycledViewPool(ViewHolder holder) {
            ViewCompat.setAccessibilityDelegate(holder.itemView, null);
            //如果回收監聽器RecyclerListener不為空,這裡會調用RecyclerListener的onViewRecycled()方法
            dispatchViewRecycled(holder
            //将 ViewHoder RecyclerView指向置空
            holder.mOwnerRecyclerView = null;
            //getRecycledViewPool()會擷取目前 RecyclerView對象所對應的RecycledViewPool對象
            getRecycledViewPool().putRecycledView(holder);
        }
           

RecycledViewPool幹什麼了?

public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList scrapHeap = getScrapHeapForType(viewType);
            //如果此種 type緩存的ViewHolder 數量超過上限就不再緩存了
            if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
                return;
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }

    //根據 viewType 擷取對應viewType的ArrayList<ViewHolder> 
      private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
      //mScrap是一個SparseArray<ArrayList<ViewHolder>> 對象,其實就是一個viewType與ArrayList<ViewHolder>一一對應的 Map;
            ArrayList<ViewHolder> scrap = mScrap.get(viewType);
            if (scrap == null) {
                scrap = new ArrayList<ViewHolder>();
                mScrap.put(viewType, scrap);
                //mMaxScrap是一個SparseIntArray對象,它儲存了每種viewType對應的緩存ViewHolder的上限數量(預設是5)
                if (mMaxScrap.indexOfKey(viewType) < ) {
                    mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
                }
            }
            return scrap;
        }
           

到此RecylerView的回收機制算是分析完了,其實 RecyclerView 的回收機制有點像”二級緩存”,首先是mCachedViews(ArrayList),如果mCachedViews滿了(上限為2+1(SDK>=21)),會把mCachedViews中的最前面的 ViewHolder 根據 type放入對應 Pool中,每種 type 對應的 Pool容量上限預設為5,超過了就不再緩存了,将超出的放入 Pool中後,最新待回收的 ViewHolder 會放入mCachedViews中。那麼mCachedViews與Pool最大的差別在那呢?其實最大的差別就是複用時的原則,mCachedViews是根據 Position進行複用的,而 Pool是根據 type 進行複用的。

分析完了緩存政策,那麼來簡單的看看是如何取用的。還記得上面在講 LinearLayoutManage.layoutChunk() 時調用的一個方法嘛?

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {
        //擷取childView
        View view = layoutState.next(recycler);
       //********************************省略*********************************
    }
           

就是這個next方法,LayoutState為LinearLayoutManage的一個靜态内部類:

LayoutState.next()

View next(RecyclerView.Recycler recycler) {
  //如果mScrapList不為空,則從mScrapList中找,通過 Position比對
  //所有正在與 RecyclerView 分離(正在跑 removed動畫)的 ViewHolder都會在mScrapList中
  //eg:例如第10個 item 正在與RecyclerView分離,你又回到了第10個 item,那麼就會從mScrapList中取,它們的位置是對應的
            if (mScrapList != null) {
                return nextViewFromScrapList();
            }
            final View view = recycler.getViewForPosition(mCurrentPosition);

            mCurrentPosition += mItemDirection;
            return view;
        }
           

Recycler

public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
        }

View getViewForPosition(int position, boolean dryRun) {
            //FOREVER_NS:永不逾時
            return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
        }

  @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
            if (position <  || position >= mState.getItemCount()) {
                throw new IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount());
            }
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            // 1. 從mChangedScrap找合适的 ViewHolder
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 2. 根據 Position從mAttachedScrap或 mHiddenViews 或 mCachedViews 中找ViewHolder
            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
                        if (!dryRun) {
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition <  || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "+ "position " + position + "(offset:" + offsetPosition + ")."+ "state:" + mState.getItemCount());
                }

                final int type = mAdapter.getItemViewType(offsetPosition);
                // 3. 根據id從mAttachedScrap或  mCachedViews 中找ViewHolder,mAdapter.hasStableIds()預設傳回 false
                if (mAdapter.hasStableIds()) {
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
                    if (holder != null) {
                        holder.mPosition = offsetPosition;
                        fromScrapOrHiddenOrCache = true;
                    }
                }
                //4. 從mViewCacheExtension中擷取中找ViewHolder,但mViewCacheExtension預設為 null
                //你可以實作ViewCacheExtensionrm抽象類,然後自定義些緩存政策,但基本不用。
                //但是 RecyclerView緩存時不會把 View放到這裡,需要自己實作
                if (holder == null && mViewCacheExtension != null) {
                    final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        if (holder == null) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned  a view which does not have a ViewHolder");
                        } else if (holder.shouldIgnore()) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view that is ignored. You must call stopIgnoring before  returning this view.");
                        }
                    }
                }
                //5. 根據type從mRecyclerPool中擷取ViewHolder
                if (holder == null) {
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }

                if (holder == null) {
                    long start = getNanoTime();
                    if (deadlineNs != FOREVER_NS&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                       //如果逾時直接傳回 null
                        return null;
                    }
                    //6. 我們熟悉的createViewHolder()方法
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                }
            }

          //**************************省略********************
          //下面主要是設定itemView的LayoutParams
            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                holder.mPreLayoutPosition = position;
            } 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);
            }

            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 的流程如下:

1. 從mChangedScrap找合适的 ViewHolder;

2. 根據 Position從mAttachedScrap或 mHiddenViews 或 mCachedViews 中找ViewHolder;

3. 如果mAdapter.hasStableIds(),根據id從mAttachedScrap或 mCachedViews 中找ViewHolder;

4. 從mViewCacheExtension中擷取中找ViewHolder,但mViewCacheExtension預設為 null;

5. 根據type從mRecyclerPool中擷取ViewHolder;

6. 建立新的 ViewHolder,createViewHolder();

至此,dispatchLayoutStep2()終于講完了,但這裡留下幾個問題。

1. mAttachedScrap是什麼?(這是一個過度狀态的 ViewHolder,多次 Measure)

2. mChangedScrap是什麼?(這是在過度狀态的被改變的VeiwHolder)

3. StableIds是什麼?

dispatchLayoutStep2()結束了,那麼 onMeasure()也結束了,下面看onLayout():

Recycler.onLayout()

TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
           

Recycler.dispatchLayout()

void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            return;
        }
        mState.mIsMeasuring = false;
        //onMeasure時中dispatchLayoutStep2跑完之後mState.mLayoutStep的狀态已經成了State.STEP_ANIMATIONS
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } 
        //如果在 onMeasure 之後 View的尺寸有變化,則重新測量
        else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() ||mLayout.getHeight()!= getHeight()) {
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }
           

Recycler.dispatchLayoutStep3()

private void dispatchLayoutStep3() {
   //********************************省略*********************************
    if (mState.mRunSimpleAnimations) {
        //找出目前需要改變的 ViewHolder,處理過渡動畫
        for (int i = mChildHelper.getChildCount() - ; i >= ; 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  過渡動畫
                final boolean oldDisappearing = mViewInfoStore.isDisappearing(oldChangeViewHolder);
                final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
                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);
            }
        }

        // 開始循環跑動畫
        mViewInfoStore.process(mViewInfoProcessCallback);
    }
    //********************************省略*********************************
}
           

這一步是與dispatchLayoutStep1呼應的,此時由于子View都已完成布局,是以子View的資訊都發生了變化。dispatchLayoutStep1出現的mItemAnimator 和mViewInfoStore再次登場,這次mItemAnimator調用的是 recordPostLayoutInformation,dispatchLayoutStep1調用的是recordPreLayoutInformation方法。

而mViewInfoStore調用的是addToPostLayout方法,dispatchLayoutStep1調用的是addToPreLayout方法,也就是真正布局之前的狀态,而現在要記錄布局之後的狀态,addToPostLayout和第一步的addToPreLayout類似,不過這次info資訊被指派給了record的postInfo變量,這樣,一個InfoRecord中就包含了布局前後(postInfo&preInfo)view的狀态。

最後,mViewInfoStore調用了process方法,這個方法就是根據mViewInfoStore中的View資訊,來執行動畫邏輯。

接下來看 onDraw():

RecyclerView.onDraw()

@Override
    public void onDraw(Canvas c) {
        super.onDraw(c);
        final int count = mItemDecorations.size();
        for (int i = ; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }
           

RecyclerView的 onDraw方法比較簡單,主要是畫ItemDecoration,至于childView的Draw都交給了 child 自己的 onDraw()。

可以看看在上面的layoutChunk()方法中調用了layoutDecoratedWithMargins方法:

public void layoutDecoratedWithMargins(View child, int left, int top, int right,
                int bottom) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Rect insets = lp.mDecorInsets;
            //child 在 layout時,去掉了ItemDecoration的區域,隻負責自己本身的 layout
            child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,right - insets.right - lp.rightMargin, bottom - insets.bottom - lp.bottomMargin);
        }
           

關于ItemDecoration

  1. getItemOffsets中為outRect設定的4個方向的值,将被計算進所有 decoration的尺寸中,而這個尺寸,會被計入了RecyclerView每個item view的padding中;
  2. 在onDraw為divider設定繪制範圍,并繪制到canvas上,而這個繪制範圍可以超出在getItemOffsets中設定的範圍,但由于decoration是繪制在child view的底下,是以并不可見,但是會存在 overdraw;
  3. decoration的onDraw,child view的onDraw,decoration的onDrawOver,這三者是依次發生的,onDrawOver是繪制在最上層的,是以它的繪制位置并不受限制;

繼續閱讀