1. 程式人生 > >RecyclerView與ListView對比淺析(二):View快取篇

RecyclerView與ListView對比淺析(二):View快取篇

(二)View快取篇

1. AbsListView(原始碼版本4.4)

RecyclerBin是AbsListView中專門處理View快取的類,官方註釋中說明其存有兩組View——ActiveViews和ScrapViews,前者是當前Layout中正在顯示的View,後者是已在螢幕範圍外可重用的View,還有一組TransientStateViews屬於ScrapViews的特殊情況,在查詢View時會用到。

變數:

mRecyclerListener:當發生View回收時,mRecyclerListener若有註冊,則會通知給註冊者.RecyclerListener介面只有一個函式onMovedToScrapHeap,指明某個view被回收到了scrapheap. 該view不再被顯示,任何相關的昂貴資源應該被丟棄。該函式是處理回收時view中的資源釋放。

mFirstActivePosition:儲存在mActiveViews中的第一個view的位置,即getFirstVisiblePosition。

mActiveViews:佈局開始時螢幕顯示的view,這個陣列會在佈局開始時填充,佈局結束後所有view被移至mScrapViews。

mScrapViews:可以被介面卡用作convertview的無序view陣列。這個ArrayList就是adapter中getView方法中的引數convertView的來源。注意:這裡是一個數組,因為如果adapter中資料有多種型別,那麼就會有多個ScrapViews。

mViewTypeCount:

view型別總數,列表中可能有多種資料型別,比如內容資料和分割符。

mCurrentScrap:跟mScrapViews的區別是,mScrapViews是個佇列陣列,ArrayList<View>[]型別,陣列長度為mViewTypeCount,而預設ViewTypeCount= 1的情況下mCurrentScrap=mScrapViews[0]。

下面三個引數分別對應addScrapView中scrapHasTransientState的三個情況(這種情況的View一般用於動畫播放):

mTransientStateViews:如果資料未變化,則可在老位置進行復用

mTransientStateViewsById:

如果Adapter有StableID,則可以對相同的資料複用View

mSkippedScrap:其他情況下,只能Remove掉再Create了。

    下面分析下RecyclerBin在AbsListListView的呼叫場景:

   (1)onLayout中,如果changed為真,會調markChildrenDirty():為每個ScrapView和TransientStateView呼叫forceLayout()。forceLayout()是將mScrapView中回收回來的View設定一樣標誌,在下次被複用到ListView中時,告訴viewroot重新layout該view。forceLayout()方法只是設定標誌,並不會通知其parent來重新layout。
        public void markChildrenDirty() {
            if (mViewTypeCount == 1) {
                final ArrayList<View> scrap = mCurrentScrap;
                final int scrapCount = scrap.size();
                for (int i = 0; i < scrapCount; i++) {
                    scrap.get(i).forceLayout();
                }
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList<View> scrap = mScrapViews[i];
                    final int scrapCount = scrap.size();
                    for (int j = 0; j < scrapCount; j++) {
                        scrap.get(j).forceLayout();
                    }
                }
            }
            if (mTransientStateViews != null) {
                final int count = mTransientStateViews.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViews.valueAt(i).forceLayout();
                }
            }
            if (mTransientStateViewsById != null) {
                final int count = mTransientStateViewsById.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViewsById.valueAt(i).forceLayout();
                }
            }
        }

   (2)onDetachedFromWindow中,會調clear():移除所有mScrapViews和mTransientViews裡的快取View

   (3)檢測滑動函式trackMotionScroll中,呼叫addScrapView(child,position)把滑動時離開螢幕範圍的View加入到相應型別的ScrapViews中,當View有TransientState的時候,根據上面變數描述中說明的情況分別存到不同List中。執行完add的迴圈後,如果有add操作即count>0,調removeSkippedScrap():移除SkippedScrap中所有Detached的View,然後clear掉SkippedScrap。

   (4)handleDataChanged中,調clearTransientStateViews:清掉mTransientStateViews和mTransientStateViewsById裡存的View。(這裡有個TODO,可以用帶有StableID的Adapter來取代兩個List)

   (5)obtainView,這是RecyclerBin的主要使用場景,用於獲取符合條件的快取View,傳入Adapter的getView進行處理。先看原始碼
    View obtainView(int position, boolean[] isScrap) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        isScrap[0] = false;
        View scrapView;

        scrapView = mRecycler.getTransientStateView(position);
        if (scrapView == null) {
            scrapView = mRecycler.getScrapView(position);
        }

        View child;
        if (scrapView != null) {
            child = mAdapter.getView(position, scrapView, this);

            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }

            if (child != scrapView) {
                mRecycler.addScrapView(scrapView, position);
                if (mCacheColorHint != 0) {
                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
                }
            } else {
                isScrap[0] = true;

                // 清除所有系統管理的Transient狀態以便回收並繫結到其他資料上
                if (child.isAccessibilityFocused()) {
                    child.clearAccessibilityFocus();
                }

                child.dispatchFinishTemporaryDetach();
            }
        } else {
            child = mAdapter.getView(position, null, this);

            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }

            if (mCacheColorHint != 0) {
                child.setDrawingCacheBackgroundColor(mCacheColorHint);
            }
        }

        if (mAdapterHasStableIds) {
            final ViewGroup.LayoutParams vlp = child.getLayoutParams();
            LayoutParams lp;
            if (vlp == null) {
                lp = (LayoutParams) generateDefaultLayoutParams();
            } else if (!checkLayoutParams(vlp)) {
                lp = (LayoutParams) generateLayoutParams(vlp);
            } else {
                lp = (LayoutParams) vlp;
            }
            lp.itemId = mAdapter.getItemId(position);
            child.setLayoutParams(lp);
        }

        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            if (mAccessibilityDelegate == null) {
                mAccessibilityDelegate = new ListItemAccessibilityDelegate();
            }
            if (child.getAccessibilityDelegate() == null) {
                child.setAccessibilityDelegate(mAccessibilityDelegate);
            }
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return child;
    }

具體過程如下:

a. 先調getTransientStateView(position)找TransientStateView,看Adapter有沒有StableId,有就從byId的List裡找,沒有就從mTransientStateView裡找。

b. 如果拿到了scrapView,連同position傳入Adapter的getView,拿到一個回撥的View——child。如果child不等於scrapView,則把它add進ScrapViews,再設下CacheColorHint;如果相等,移除它的TransientState,以便進行回收

c. 如果沒拿到scrapView,給Adapter的getView傳個null進去,讓其在內部新建View並返回,拿到返回View——child,設下CacheColor。

d. 如果Adapter有StableId,再設下child的LayoutParams,這裡ItemId也作為LayoutParams的變數存入。

e. 返回child。


2. ListView(原始碼版本 4.4)

我們還是從RecyclerBin的呼叫來看ListView裡的View快取部分:

   (1)Adapter變更後,Recycler就要重置,setAdapter中,先調clear()重置Recycler,調完super.setAdapter後,調setViewTypeCount(typeCount)初始化:有幾種type,就new幾個ArrayList<View>()。

        public void setViewTypeCount(int viewTypeCount) {
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            }
            //noinspection unchecked
            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
            for (int i = 0; i < viewTypeCount; i++) {
                scrapViews[i] = new ArrayList<View>();
            }
            mViewTypeCount = viewTypeCount;
            mCurrentScrap = scrapViews[0];
            mScrapViews = scrapViews;
        }

   (2) LayoutChildren中分三步,第一步:如果資料改變,則把當前所有childView加進ScrapView,如果沒改變,那就用當前的View填滿ActiveViews;第二步:新增View到ListView;第三步:回收ActiveViews中未用到的View到ScrapView中。在第一步中,如果是addScrapView,則所有的view將會detach,如果是fillActiveViews,則不會detach,只有在第三步中,未用到的view才會detach。

    @Override
    protected void layoutChildren() {
        final boolean blockLayoutRequests = mBlockLayoutRequests;
        if (blockLayoutRequests) {
            return;
        }
        mBlockLayoutRequests = true;
        try {
            super.layoutChildren();
            invalidate();
            if (mAdapter == null) {
                resetList();
                invokeOnItemScrollListener();
                return;
            }
            final int childrenTop = mListPadding.top;
            final int childrenBottom = mBottom - mTop - mListPadding.bottom;
            final int childCount = getChildCount();

            int index = 0;
            int delta = 0;
            ……

            // 第一步:所有子View放入RecycleBin,可能的話會進行重用
            final int firstPosition = mFirstPosition;
            final RecycleBin recycleBin = mRecycler;
            if (dataChanged) {
                for (int i = 0; i < childCount; i++) {
                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                }
            } else {
                recycleBin.fillActiveViews(childCount, firstPosition);
            }

            //清除舊View
            detachAllViewsFromParent();
            recycleBin.removeSkippedScrap();

            switch (mLayoutMode) {
             ……
            default:
            // 第二步,預設LayoutMode下的填充操作
                if (childCount == 0) {
                    if (!mStackFromBottom) {
                        final int position = lookForSelectablePosition(0, true);
                        setSelectedPositionInt(position);
                        sel = fillFromTop(childrenTop);
                    } else {
                        final int position = lookForSelectablePosition(mItemCount - 1, false);
                        setSelectedPositionInt(position);
                        sel = fillUp(mItemCount - 1, childrenBottom);
                    }
                } else {
                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                        sel = fillSpecific(mSelectedPosition,
                                oldSel == null ? childrenTop : oldSel.getTop());
                    } else if (mFirstPosition < mItemCount) {
                        sel = fillSpecific(mFirstPosition,
                                oldFirst == null ? childrenTop : oldFirst.getTop());
                    } else {
                        sel = fillSpecific(0, childrenTop);
                    }
                }
                break;
            }

            // 第三步:把上面沒用到的ActiveView都丟到ScrapViews中
            recycleBin.scrapActiveViews();
           ……
    }

   (3)makeAndAddView,這個函式拿到View放到ChildView的List中。如果資料沒有更新,使用ActiveViews裡的View,這些View不需要重新Measure即可使用。如果資料改變了,調父類的obtainView拿View,這裡會new或者重用。
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;
        if (!mDataChanged) {
            // 首先看能不能用現成的View
            child = mRecycler.getActiveView(position);
            if (child != null) {
                // 找到,剩下就只需要設它的position了
                setupChild(child, position, y, flow, childrenLeft, selected, true);
                return child;
            }
        }

        // 調obtainView,有可能新建View或在快取View中重用
        child = obtainView(position, mIsScrap);
        // 這種View不僅需要設position,還需要measure
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
        return child;
    }

   (4)scrollListItemsBy,這個函式用於對子view滑動一定距離,新增view到底部或者移除頂部的不可見view。當可見的第一個ItemView劃出List頂或最後一個itemView劃出List底時,調shouldRecycleViewType判斷是否需要回收,如果需要就調addScrapView進行回收。

    private void scrollListItemsBy(int amount) {
        offsetChildrenTopAndBottom(amount);

        final int listBottom = getHeight() - mListPadding.bottom;
        final int listTop = mListPadding.top;
        final AbsListView.RecycleBin recycleBin = mRecycler;

       if (amount < 0) {
        ……
            // 首個View超出頂部的情況
            View first = getChildAt(0);
            while (first.getBottom() < listTop) {
                AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
                if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
                    recycleBin.addScrapView(first, mFirstPosition);
                }
                detachViewFromParent(first);
                first = getChildAt(0);
                mFirstPosition++;
            }
        } else {
            ……
            // 末位View超出底部的情況
            while (last.getTop() > listBottom) {
                AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
                if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
                    recycleBin.addScrapView(last, mFirstPosition+lastIndex);
                }
                detachViewFromParent(last);
                last = getChildAt(--lastIndex);
            }
        }
    }

    從上面可以看出,Android中view回收的計算是其父view中不再顯示的,如果scrollview中包含了一個wrap_content屬性的listview,裡面的內容並不會有任何回收,引起listview 的getheight函式獲取的是一個足以顯示所有內容的高度。


3. RecyclerView(原始碼版本 5.1.1)

RecyclerView直接繼承的ViewGroup,沒有使用ListVIew的RecyclerBin,而是重新定義了一個自己的回收類Recycler,裡面儲存的不是View,而是ViewHolder。下面來詳細分析一下:

3.3.1 變數:

mAttachedScrap:這裡並沒有ActvieViews的概念,而是用AttachedScrap來代替,AttachedScrap也沒有用ArrayList陣列來儲存不同型別的ViewHolder,只用了一個ArrayList<ViewHolder>。

mChangedScrap:與AttachedScrap對應,表示資料已改變的ViewHolder。

mCachedViews:這是RecyclerView快取的第二層,在記憶體中儲存待重用的快取ViewHolder,其大小由mViewCacheMax決定,預設DEFAULT_CACHE_SIZE為2,可動態設定。

mUnmodifiableAttachedScrap:只在getScrapList作為返回量返回,看字面意思是不可變的AttachedScrap。

mRecyclerPool:這是RecyclerView快取的第三層,在有限的mCachedViews中如果存不下ViewHolder時,就會把ViewHolder存入RecyclerViewPool中,其中用SparseArray<ArrayList<ViewHolder>>的結構分viewType儲存ViewHolder。

mViewCacheExtension:這是開發者可自定義的一層快取,是虛擬類ViewCacheExtension的一個例項,開發者可實現函式getViewForPositionAndType(Recycler recycler, int position, int type)來實現自己的快取。

3.3.2 mRecycler的關鍵呼叫場景:

   (1)setAdapter中,如果與原Adapter不相容或需強制重置,LayoutManager的例項mLayout會調removeAndRecycleAllViews(mRecycler)和removeAndRecycleScrapInt(mRecycler)重置mRecycler,mRecycler再調clear()重置。不管是否執行前面這步,mRecycler都會調onAdapterChanged進行處理,這個函式裡會先調clear():清理mAttachedScraps、mCachedViews,把mCachedViews中的View都存入RecyclerPool;然後mRecyclerPool也會調onAdapterChanged,根據條件判斷選擇呼叫detach()、clear()和attach()。

    private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            // 動畫終止
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
            }
            // 此時mLayout.children應該等於recyclerView.children
            if (mLayout != null) {
                mLayout.removeAndRecycleAllViews(mRecycler);
                mLayout.removeAndRecycleScrapInt(mRecycler);
            }
            // 為保證回撥正確,需清理
            mRecycler.clear();
        }
        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;
        markKnownViewsInvalid();
    }

   (2)setLayoutManager中,如果是更換LayoutManager,則調mLayout的onDetachedFromWIndow進行分離,入參包括mRecycler。再調mRecycler的clear()重置。

   (3)addAnimatingView中,在儲存View到mChildHelper之前,會對其所屬ViewHolder進行unscrap操作,調unscrapView:根據holder是否已變等引數,在mAttachedScrap或mChangedScrap中移除holder,並清除Holder中“來自Scrap”的標誌位returnedFromScrapFlag。removeAnimatingView中,若View變為hidden,則,調unscrapView處理Viewholder,然後調recycleViewHolderInternal(viewHolder):如果mCachedViews沒滿,則add進去,如果滿了,就放到RecyclerPool。這是常見的快取ViewHolder使用流程

    private void addAnimatingView(ViewHolder viewHolder) {
        final View view = viewHolder.itemView;
        final boolean alreadyParented = view.getParent() == this;
        mRecycler.unscrapView(getChildViewHolder(view));
        if (viewHolder.isTmpDetached()) {
            //重新關聯
            mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
        } else if(!alreadyParented) {
            mChildHelper.addView(view, true);
        } else {
            mChildHelper.hide(view);
        }
    }

3.3.3 getViewForPosition(position)

這個函式在RecyclerView的trace中佔比較高,僅低於LayoutManager的操作和RecyclerView的Touch和Scroll,是尋找快取View的主要過程。下面詳細分析下:

 (1)如果mChangedScrap不為空,遍歷mChangedScrap尋找合適的Holder(條件是mState.isPreLayout為真)

 (2)根據position,在Scrap裡尋找。先從mAttachedScrap中遍歷尋找合適的Holder,若未找到,則遍歷mCachedViews尋找合適的holder,若都沒有則返null。找到合適的Holder後,驗證它的position和type,若驗證失敗,兩種情況,如果isScrap為true,進行removeDetach操作,然後再unScrap,如果wasReturnedFromScrap,清除相關標誌位,最後對Holder進行Recycle操作(先對holder進行一系列驗證後,若符合條件:如果當前mCachedViews的size等於上限,那回收位置為0的holder,也就是最老的,回收後放入RecycledViewPool中;若不符合條件,直接放入RecycledViewPool);若驗證成功,fromScrap置true。

  (3)如果上面沒找到合適的,holder依舊為null,再看Adapter有沒有StableId,有就根據Id在Scrap裡找(這時ID和position相等)。

  (4)如果還沒有合適的,看mViewCacheExtension,看解釋像是開發者可以自定義的Cache,有就調它的getViewForPositionAndType方法找,這裡需要開發者自己定義。

  (5)還沒找到,就準備在RecyclerPool裡找,如果找到,重置這個holder的多個標誌位(如mPosition、mItemId等),如果FORCE_INVALIDATE_DISPLAY_LIST為true(SDK是19或20則為true),就invalidate一下ViewGroup

  (6)如果還沒找到,就調Adapter的createViewHolder再BindViewHolder了。
View getViewForPosition(int position, boolean dryRun) {
            if (position < 0 || position >= mState.getItemCount()) {
                throw new IndexOutOfBoundsException("Invalid item position " + position
                        + "(" + position + "). Item count:" + mState.getItemCount());
            }
            boolean fromScrap = false;
            ViewHolder holder = null;
            //有預Layout的話,先從ChangedScrap裡找ViewHolder
            if (mState.isPreLayout()) {					
                holder = getChangedScrapViewForPosition(position);
                fromScrap = holder != null;
            }
            //在Scrap中根據位置找
            if (holder == null) {
                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle this scrap
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            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 {
                        fromScrap = true;
                    }
                }
            }
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                            + "position " + position + "(offset:" + offsetPosition + ")."
                            + "state:" + mState.getItemCount());
                }

                final int type = mAdapter.getItemViewType(offsetPosition);
                //如果Adapter有StableId,試著用ID找
                if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                    if (holder != null) {
                        // 更新位置
                        holder.mPosition = offsetPosition;
                        fromScrap = true;
                    }
                }
                // 如果有自定義快取,再在這裡找
                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.");
                        }
                    }
                }
                if (holder == null) { // 回到Recycler中尋找
                    //在回收池裡尋找
                    
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                                + "pool");
                    }
                    holder = getRecycledViewPool()
                            .getRecycledView(mAdapter.getItemViewType(offsetPosition));
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                //都沒找到,就只能Create了
                if (holder == null) {
                    holder = mAdapter.createViewHolder(RecyclerView.this,
                            mAdapter.getItemViewType(offsetPosition));
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition created new ViewHolder");
                    }
                }
            }
            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                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);
                //繫結資料,執行更新
                mAdapter.bindViewHolder(holder, offsetPosition);
                attachAccessibilityDelegate(holder.itemView);
                bound = true;
                if (mState.isPreLayout()) {
                    holder.mPreLayoutPosition = position;
                }
            }

            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 = fromScrap && bound;
            return holder.itemView;
        }

3.3.4 快取規則

當一個ViewHolder需要快取時一般需要下面幾個判斷步驟:

  (1)if(viewHolder.shouldIgnore()),如果為true則直接返回。

  (2)if(viewHolder.isInvalid()&& !viewHolder.isRemoved() && !viewHolder.isChanged()&& !mRecyclerView.mAdapter.hasStableIds()),如果為true,則走Recycle操作,如果為false,則走Scrap操作。

  (3)Recycle操作先判斷(!holder.isInvalid()&& (mState.mInPreLayout || !holder.isRemoved())&&!holder.isChanged()),為true則判斷mCachedViews是否已滿,滿了就退休調位置為0的View存入Recycler Pool,沒滿就把holder存入;為false就直接存Recycler Pool。

  (4)Scrap操作,先調holder.setScrapContainer(this),在自己的mScrapContainer中記錄下自己已Scrap的狀態,然後判斷(!holder.isChanged()|| !supportsChangeAnimations()):true後再判斷(holder.isInvalid() && !holder.isRemoved() &&!mAdapter.hasStableIds()),若滿足則丟擲異常,不滿足則加進mAttachedScrap;false後,加進mChangedScrap。