啥是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作為RecyclerView內部復用的單位,直接避免了不必要的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
這兩個方法從字面上看就是創建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)
*/
當RecyclerView需要一個新的類型的item的ViewHolder的時候調用這個方法。
第二段描述是講如何創建這個ViewHolder,跳過。
新的ViewHolder將會被用來通過adapter調用onBindViewHolder展示item。由于它將會被復用去展示在數據集中的不同items,所以緩存View的子view引用去避免不必要的對findViewById方法的調用是一個好主意。
看了上面的這段話,我產生了一個疑問,第一段話的意思仿佛是只有在需要新的類型的ViewHolder的時候才需要調用這個方法。如果是這樣,的確可以從側面說明他是以ViewHolder為單位來實現復用的。為了驗證我的想法,我在onCreateViewHolder和onBindViewHolder方法中加入了計數的代碼,看一下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 - Find from scrap by position
如果上一步未找到ViewHolder,則從mAttachedScrap中通過position找
- Find from scrap by position
- Find from scrap via stable ids, if exists
如果上一步未找到且存在stable id,則通過id在mAttachedScrap中找ViewHolder
- Find from scrap via stable ids, if exists
- 如果上一步未找到且mViewCacheExtension不為空,則在mViewCacheExtension中找ViewHolder
- 如果上一步未找到則通過RecycledCiewPool尋找ViewHolder
- 如果上一步未找到則通過Adapter的createViewHolder創建一個新的ViewHolder
如此一來經歷了以上的步驟,一個ViewHolder便會先從緩存中讀取,如果都無法匹配到,則會新創建一個。如此便實現了ViewHolder的復用。
后記
無關技術,自己的小結,各位可以跳過~
最近有點小忙,而且也一直在看Java的一些東西,處于積累的階段吧,所以文章更新的沒以前勤快了。不過我還是希望自己能夠堅持寫下去,這次只閱讀了RecyclerView的部分源碼,感覺自己還有很多的不足,以后還會繼續探究RecyclerView的其他東西。