探究RecyclerView的ViewHolder復用

Iesha70Q 7年前發布 | 35K 次閱讀 Android開發 移動開發 RecyclerView

啥是RecyclerView

  • A flexible view for providing a limited window into a large data set.

    一個在大小有限的窗口內展示大量數據集的view。恩,我的翻譯一向不咋滴。。所以原文也放上了。

    RecyclerView網上很多文都說是用來取代ListView和GridView的,事實上RecyclerView的確可以做到ListView和GridView能做的事,而且他將ViewHolder和Adapter都作為內部類,寫在了RecyclerView中。先不管這把所有類都寫在RecyclerView內部的做法是否好,ViewHolder作為復用的單位,避免了不必要的findViewById。

一個使用RecyclerView的示例

在進行探究之前,首先回顧一下我們是如何使用一個RecyclerView的。

第一步在布局文件里加上RecyclerView:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="

<android.support.v7.widget.RecyclerView
    android:id="@+id/rv_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</LinearLayout></code></pre>

第二部,給RecyclerView的item編寫布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:src="@drawable/test" />

</LinearLayout></code></pre>

第三步,為RecyclerView寫一個Adapter:

package com.xiasuhuei321.test;

import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.support.v7.widget.RecyclerView.ViewHolder;

/**

  • Created by xiasuhuei321 on 2016/12/25.
  • author:luo
  • e-mail:xiasuhuei321@163.com */

public class TestAdapter extends RecyclerView.Adapter { Context mContext;

public TestAdapter(Context context) {
    this.mContext = context;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    return new ItemViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_test, parent, false));
}

@Override
public int getItemCount() {
    return 100;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
}

class ItemViewHolder extends ViewHolder {

    public ItemViewHolder(View itemView) {
        super(itemView);
    }
}

}</code></pre>

這里只是簡單的演示,代碼寫的都非常的簡單。。各位都不要模仿。。。

第四步,給RecyclerView設置對應的布局和Adapter:

package com.xiasuhuei321.test;

import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView;

/**

  • Created by xiasuhuei321 on 2016/12/25.
  • author:luo
  • e-mail:xiasuhuei321@163.com */

public class TestActivity extends AppCompatActivity {

private RecyclerView mList;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_test);

    mList = (RecyclerView) findViewById(R.id.rv_list);
    mList.setLayoutManager(new LinearLayoutManager(this));
    mList.setAdapter(new TestAdapter(this));
}

}</code></pre>

最后看下效果

通過以上的流程,對RecyclerView的簡單使用就過完了,在這個流程中,可以看出編寫Adapter是一個關鍵,事實上RecyclerView和ListView都一樣,都是通過Adapter來設置和管理每一個item的。

ViewHolder與復用

在復寫RecyclerView.Adapter的時候,需要我們復寫兩個方法:

  • onCreateViewHolder
  • onBindViewHolder

這兩個方法從字面上看就是創建ViewHolder和綁定ViewHolder的意思,來看一下源碼中對我們實現的這兩個方法的注釋:

/**

     * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
     * an item.
     * <p>
     * This new ViewHolder should be constructed with a new View that can represent the items
     * of the given type. You can either create a new View manually or inflate it from an XML
     * layout file.
     * <p>
     * The new ViewHolder will be used to display items of the adapter using
     * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
     * different items in the data set, it is a good idea to cache references to sub views of
     * the View to avoid unnecessary {@link View#findViewById(int)} calls.
     *
     * @param parent The ViewGroup into which the new View will be added after it is bound to
     *               an adapter position.
     * @param viewType The view type of the new View.
     *
     * @return A new ViewHolder that holds a View of the given view type.
     * @see #getItemViewType(int)
     * @see #onBindViewHolder(ViewHolder, int)
     */</code></pre> 

當RecyclerView需要一個新的類型的item的ViewHolder的時候調用這個方法。

第二段描述是講如何創建這個ViewHolder,跳過。

新的ViewHolder將會被用來通過adapter調用onBindViewHolder展示item。由于它將會被復用去展示在數據集中的不同items,所以緩存View的子view引用去避免不必要的對findViewById方法的調用是一個好主意。

看了上面的這段話,我產生了一個疑問,第一段話的意思仿佛是只有在需要新的類型的ViewHolder的時候才需要調用這個方法。如果是這樣,的確可以從側面說明他是以ViewHolder為單位來實現復用的。為了驗證我的想法,我在onCreateViewHolder和onBindViewHolder方法中加入了計數的代碼,看一下log:

log

從中可以看出并不是像我想的那樣,只調用了一次,稍微想一下也很容易想明白,因為他是通過ViewHolder復用不假,我這里只有一種ViewType,上下滑動的時候需要的ViewHolder種類是只有一種,但是需要的ViewHolder對象數量并不止一個。所以在后面創建了5個ViewHolder之后,需要的數量夠了,無論我怎么滑動,他都只需要復用以前創建的對象就行了。

在這里,感覺ViewHolder的類型和對象數量有點像Java中Class和對象的關系。Java中第一次將.class裝載入JVM虛擬機的時候,會生成一個Class對象,以后所有這個類的對象都由Class生成。是不是有點像呢?

看到了這個log之后,我的第一反應是在這個ViewHolder對象的數量“夠用”之后就停止調用onCreateViewHolder方法,看一下源碼:

/**
         * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new
         * {@link ViewHolder} and initializes some private fields to be used by RecyclerView.
         *
         * @see #onCreateViewHolder(ViewGroup, int)
         */
        public final VH createViewHolder(ViewGroup parent, int viewType) {
            TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
            final VH holder = onCreateViewHolder(parent, viewType);
            holder.mItemViewType = viewType;
            TraceCompat.endSection();
            return holder;
        }

可以發現這里并沒有限制,那么是不是在調用這個createViewHolder方法的時候做了限制呢?

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;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrap = holder != null;
            }
            // 1) Find from scrap by position
            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);
                // 2) Find from scrap via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrap = true;
                    }
                }
                if (holder == null && mViewCacheExtension != null) {
                    // 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) {
                            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) { // fallback to recycler
                    // try recycler.
                    // Head to the shared pool.
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                                + "pool");
                    }
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition created new ViewHolder");
                    }
                }
            }

            // This is very ugly but the only place we can grab this information
            // before the View is rebound and returned to the LayoutManager for post layout ops.
            // We don't need this in pre-layout since the VH is not updated by the LM.
            if (fromScrap && !mState.isPreLayout() && holder
                    .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
                holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (mState.mRunSimpleAnimations) {
                    int changeFlags = ItemAnimator
                            .buildAdapterChangeFlagsForAnimations(holder);
                    changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                            holder, changeFlags, holder.getUnmodifiedPayloads());
                    recordAnimationInfoIfBouncedHiddenView(holder, info);
                }
            }

            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);
                holder.mOwnerRecyclerView = RecyclerView.this;
                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;
        }

可以看出的確是有條件的。當然,在此不具體分析,不然可能會深入細節無法自拔。

Recycler && RecycledViewPool

說實話,上面分析完,我也有點沒方向,因為畢竟整個RecyclerView一萬多行代碼在那,不知道看哪了,不過好在網上有篇文曾干過和我差不多的事 RecyclerView源碼分析 ,前人指了條路,跟著看一下源碼好了。

Recycler:

/**
     * 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>
     */

Recycler負責管理廢棄(scrapped)或者分離(detach)的item。

scrapped指的是仍然在RecyclerView上但是已經被標記了移除或者復用。

一個對Recycler的經典的使用時LayoutManager,它通過Recycler為adapter的數據集的特定位置獲取一個view。如果這個view將被復用但被認為是“dirty”的,那么這個adapter將調用方法重新綁定它。如果不是,這個view可以迅速的被LayoutManager復用而不用進一步的處理。Clean view無需調用request layout,不需要重新測量就能復用。

RecycledViewPool:

/**
     * RecycledViewPool lets you share Views between multiple RecyclerViews.
     * <p>
     * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
     * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
     * <p>
     * RecyclerView automatically creates a pool for itself if you don't provide one.
     *
     */
    public static class RecycledViewPool {
        private SparseArray<ArrayList<ViewHolder>> mScrap =
                new SparseArray<ArrayList<ViewHolder>>();
        private SparseIntArray mMaxScrap = new SparseIntArray();
        private int mAttachCount = 0;

        private static final int DEFAULT_MAX_SCRAP = 5;

        public void clear() {
            mScrap.clear();
        }

        public void setMaxRecycledViews(int viewType, int max) {
            mMaxScrap.put(viewType, max);
            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
            if (scrapHeap != null) {
                while (scrapHeap.size() > max) {
                    scrapHeap.remove(scrapHeap.size() - 1);
                }
            }
        }

        public ViewHolder getRecycledView(int viewType) {
            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
            if (scrapHeap != null && !scrapHeap.isEmpty()) {
                final int index = scrapHeap.size() - 1;
                final ViewHolder scrap = scrapHeap.get(index);
                scrapHeap.remove(index);
                return scrap;
            }
            return null;
        }

        int size() {
            int count = 0;
            for (int i = 0; i < mScrap.size(); i ++) {
                ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i);
                if (viewHolders != null) {
                    count += viewHolders.size();
                }
            }
            return count;
        }

        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList scrapHeap = getScrapHeapForType(viewType);
            if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }

        void attach(Adapter adapter) {
            mAttachCount++;
        }

        void detach() {
            mAttachCount--;
        }


        /**
         * Detaches the old adapter and attaches the new one.
         * <p>
         * RecycledViewPool will clear its cache if it has only one adapter attached and the new
         * adapter uses a different ViewHolder than the oldAdapter.
         *
         * @param oldAdapter The previous adapter instance. Will be detached.
         * @param newAdapter The new adapter instance. Will be attached.
         * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
         *                               ViewHolder and view types.
         */
        void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
                boolean compatibleWithPrevious) {
            if (oldAdapter != null) {
                detach();
            }
            if (!compatibleWithPrevious && mAttachCount == 0) {
                clear();
            }
            if (newAdapter != null) {
                attach(newAdapter);
            }
        }

        private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
            ArrayList<ViewHolder> scrap = mScrap.get(viewType);
            if (scrap == null) {
                scrap = new ArrayList<>();
                mScrap.put(viewType, scrap);
                if (mMaxScrap.indexOfKey(viewType) < 0) {
                    mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
                }
            }
            return scrap;
        }
    }

老規矩,先看注釋:RecycledViewPool讓你在多個RecyclerView之間共享View。如果你想要在RecyclerView間循環利用view,創建一個RecyclerViewPool的實例然后調用setRecyclerViewPool方法。

如果你不提供一個那么RecyclerView將為他自己自動的創建一個RecycledViewPool。

接下來看下里面的代碼,內部有一個SparseArray一個SparseIntArray,看到這終于感覺快看到點子上了,畢竟看起來就像是兩個放東西的容器,應該是離真相不遠了。先看下SparseArray是個啥,點進去看注釋第一句就是 SparseArrays map integers to Objects ,這是一個integer和對象的映射,他內部也是有兩個數組一個是integer作為鍵的int[] mKeys的int類型的數組,另外一個是Object[] mValues的對象數組。而結合他在RecycledViewPool中的定義 SparseArray<ArrayList<ViewHolder>> mScrap;這種定義,表明是一個integer映射ViewHolder的集合。這個該怎么理解呢?在我們的實際使用中,很可能會有非常多種類的viewType,那么這個時候同一類的ViewHolder就保存在同一個ArrayList中,而在RecyclerView內部ViewType都是通過int類型的數字來代表的,正好符合。由此可以大致可以確定這個mScrap就是保存ViewHolder的關鍵了。

而SparseIntArray則是Integer映射Integer,在這可以結合setMaxRecycledViews方法中的第一行代碼 mMaxScrap.put(viewType,max),可以看出這是表明了一種ViewType對應的可保存對象集合的最大尺寸。

大致了解了下RecycledViewPool,然后回頭去看一下之前被我跳過的getViewForPosition:

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;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrap = holder != null;
            }
            // 1) Find from scrap by position
            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);
                // 2) Find from scrap via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrap = true;
                    }
                }
                if (holder == null && mViewCacheExtension != null) {
                    // 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) {
                            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) { // fallback to recycler
                    // try recycler.
                    // Head to the shared pool.
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                                + "pool");
                    }
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition created new ViewHolder");
                    }
                }
            }

            // This is very ugly but the only place we can grab this information
            // before the View is rebound and returned to the LayoutManager for post layout ops.
            // We don't need this in pre-layout since the VH is not updated by the LM.
            if (fromScrap && !mState.isPreLayout() && holder
                    .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
                holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (mState.mRunSimpleAnimations) {
                    int changeFlags = ItemAnimator
                            .buildAdapterChangeFlagsForAnimations(holder);
                    changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                            holder, changeFlags, holder.getUnmodifiedPayloads());
                    recordAnimationInfoIfBouncedHiddenView(holder, info);
                }
            }

            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);
                holder.mOwnerRecyclerView = RecyclerView.this;
                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;
        }

在具體分析這個方法之前,先給出這個類內部幾個參數的大致意思:

private ArrayList<ViewHolder> mAttachedScrap

private ArrayList<ViewHolder> mChangedScrap 與RecyclerView分離的ViewHolder列表。

private ArrayList<ViewHolder> mCachedViews ViewHolder緩存列表。

private ViewCacheExtension mViewCacheExtension 開發者控制的ViewHolder緩存

private RecycledViewPool mRecyclerPool 提供復用ViewHolder池。

可以看到源碼中已經給了我們步驟提示:

  • If there is a changed scrap, try to find from there
    從mChangedScrap中尋找ViewHolder
  • 1) Find from scrap by position
    如果上一步未找到ViewHolder,則從mAttachedScrap中通過position找
  • 2) Find from scrap via stable ids, if exists
    如果上一步未找到且存在stable id,則通過id在mAttachedScrap中找ViewHolder
  • 如果上一步未找到且mViewCacheExtension不為空,則在mViewCacheExtension中找ViewHolder
  • 如果上一步未找到則通過RecycledCiewPool尋找ViewHolder
  • 如果上一步未找到則通過Adapter的createViewHolder創建一個新的ViewHolder

如此一來經歷了以上的步驟,一個ViewHolder便會先從緩存中讀取,如果都無法匹配到,則會新創建一個。如此便實現了ViewHolder的復用。

 

 

來自:http://www.jianshu.com/p/d7ec36aa8e4b

 

 本文由用戶 Iesha70Q 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!