探究RecyclerView的ViewHolder復用

啥是RecyclerView

  • A flexible view for providing a limited window into a large data set.
    一個在大小有限的窗口內(nèi)展示大量數(shù)據(jù)集的view。恩,我的翻譯一向不咋滴。。所以原文也放上了。

RecyclerView網(wǎng)上很多文都說是用來取代ListView和GridView的,事實上RecyclerView的確可以做到ListView和GridView能做的事,而且他將ViewHolder和Adapter都作為內(nèi)部類,寫在了RecyclerView中。先不管這把所有類都寫在RecyclerView內(nèi)部的做法是否好,但是ViewHolder作為RecyclerView內(nèi)部復用的單位,直接避免了不必要的findViewById,而在ListView中則需要我們自己定義ViewHolder。

一個使用RecyclerView的示例

在進行探究之前,首先回顧一下我們是如何使用一個RecyclerView的。
第一步在布局文件里加上RecyclerView:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

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

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="8dp">

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

</LinearLayout>

第三步,為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);
        }
    }

}

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

第四步,給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));
    }
}

最后看下效果

呆毛王

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

ViewHolder與復用

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

  • onCreateViewHolder
  • onBindViewHolder

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

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

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

第二段描述是講如何創(chuàng)建這個ViewHolder,跳過。

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

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

log

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

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

看到了這個log之后,我的第一反應是在這個ViewHolder對象的數(shù)量“夠用”之后就停止調(diào)用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;
        }

可以發(fā)現(xiàn)這里并沒有限制,那么是不是在調(diào)用這個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;
        }

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

Recycler && RecycledViewPool

說實話,上面分析完,我也有點沒方向,因為畢竟整個RecyclerView一萬多行代碼在那,不知道看哪了,不過好在網(wǎng)上有篇文曾干過和我差不多的事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上但是已經(jīng)被標記了移除或者復用。

一個對Recycler的經(jīng)典的使用時LayoutManager,它通過Recycler為adapter的數(shù)據(jù)集的特定位置獲取一個view。如果這個view將被復用但被認為是“dirty”的,那么這個adapter將調(diào)用方法重新綁定它。如果不是,這個view可以迅速的被LayoutManager復用而不用進一步的處理。Clean view無需調(diào)用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;
        }
    }

老規(guī)矩,先看注釋:RecycledViewPool讓你在多個RecyclerView之間共享View。如果你想要在RecyclerView間循環(huán)利用view,創(chuàng)建一個RecyclerViewPool的實例然后調(diào)用setRecyclerViewPool方法。

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

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

在具體分析這個方法之前,先給出這個類內(nèi)部幾個參數(shù)的大致意思:
private ArrayList<ViewHolder> mAttachedScrap
private ArrayList<ViewHolder> mChangedScrap 與RecyclerView分離的ViewHolder列表。
private ArrayList<ViewHolder> mCachedViews ViewHolder緩存列表。
private ViewCacheExtension mViewCacheExtension 開發(fā)者控制的ViewHolder緩存
private RecycledViewPool mRecyclerPool 提供復用ViewHolder池。

可以看到源碼中已經(jīng)給了我們步驟提示:

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

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

后記

無關技術,自己的小結,各位可以跳過~
最近有點小忙,而且也一直在看Java的一些東西,處于積累的階段吧,所以文章更新的沒以前勤快了。不過我還是希望自己能夠堅持寫下去,這次只閱讀了RecyclerView的部分源碼,感覺自己還有很多的不足,以后還會繼續(xù)探究RecyclerView的其他東西。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容