本文有分析錯誤的地方,recyclerView真複雜.我也是把自己大概知道的地方整理了一下,沒有形成系統的,有明確定義的文章.僅供參考.
RecyclerView setAdapter()的過程
public class RecyclerView extends ViewGroup {
private static final String TAG = "RecyclerView";
// ..............省略代碼
public void setAdapter(Adapter adapter) {
setAdapterInternal(adapter, false, true);
requestLayout();
}
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
if (mLayout != null) {
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler, true);
}
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
//註冊內容觀察者
adapter.registerAdapterDataObserver(mObserver);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
//設置結構發生改變的標誌位
mState.mStructureChanged = true;
// 刷新視圖
markKnownViewsInvalid();
}
//省略代碼 ............
}
//觀察者處理佈局數據
private class RecyclerViewDataObserver extends AdapterDataObserver {
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
if (mAdapter.hasStableIds()) {
mState.mStructureChanged = true;
mDataSetHasChangedAfterLayout = true;
} else {
mState.mStructureChanged = true;
mDataSetHasChangedAfterLayout = true;
}
//需要重新佈局
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
我們看到從setAdapter()之後,會調用到RecyclerViewDataObserver 的onChanged 函數,在該函數中又會調用RecyclerView 的requestLayout函數進行重新佈局.而RecyclerView把佈局的任務交給了LayoutManager.
RecyclerView測量階段
1.在dispatchLayoutStep1()
主要做了進程適配器更新;決定應該運行哪個動畫;保存當前視圖的信息;如有必要運行預測佈局並保存其信息(數據變化前的更新).
2. dispatchLayoutStep2()
實際佈局的方法,會把過程交給LayoutManager裏面的onLayoutChildren()方法.
3. dispatchLayoutStep3()
佈局的最後一步,我們保存有關動畫視圖的信息,觸發動畫並進行任何必要的清理.
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
//沒有 LayoutManager的時候,設置默認的測量模式.就是那些EXACTLY,AT_MOST,UNSPECIFIED
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//有 LayoutManager,而且開啓自動測量,這個時候就可以爲 RecyclerView 設置 wrap_content 值了
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
//調用LayoutManager的方法
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
//調用LayoutManager的方法
dispatchLayoutStep2();
// 現在我們可以從子view那裏獲得寬度和高度。
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
//若 RecyclerView 已經設置了固定的值,那就執行 LayoutManager 的 onMeasure() 方法
//設置方法setAutoMeasureEnable(false)
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
eatRequestLayout();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
onExitLayoutOrScroll();
if (mState.mRunPredictiveAnimations) {
mState.mInPreLayout = true;
} else {
// consume remaining updates to provide a consistent state with the layout pass.
mAdapterHelper.consumeUpdatesInOnePass();
mState.mInPreLayout = false;
}
mAdapterUpdateDuringMeasure = false;
resumeRequestLayout(false);
} else if (mState.mRunPredictiveAnimations) {
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
return;
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
eatRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
resumeRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
//-------------------------------------------------------------------------------------------------------------------------
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
eatRequestLayout();
//分發layout
dispatchLayout();
resumeRequestLayout(false);
mFirstLayoutComplete = true;
}
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
在onLayout()與onMeasure()方法中我們大概可以得知:
1.在onMeasure()過程中,會判斷是否支持WRAP_CONTENT,如果支持那麼就會提前調用dispatchLayoutStep1,2()方法,先確定子控件的大小和位置,再設置recyclerView的大小.
2.如果不是這種情況那麼就會調用onLayout()方法去執行.RecyclerView將佈局childView的操作交給了LayoutManager,在佈局過程中,也有很多關於動畫的處理,在 dispatchLayoutStep1,2,3()這幾個過程中均有體現.因爲太過複雜,這裏沒有介紹.
ViewHolder存入緩存的過程
在使用LayoutManager這個類的時候會用到的緩存的概念.實際上我們使用recyclerView的時候也必須設置佈局管理器.
一般的我們自定義一個LayoutManager的時候,大致需要如下的模板來完成.
//一般的自定義LayoutManager模板代碼
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler,
RecyclerView.State state) {
//Scrap measure one child
//根據Adapter的位置position找到RecyclerView中對應的item
View scrap = recycler.getViewForPosition(0);
addView(scrap);
measureChildWithMargins(scrap, 0, 0);
//獲取childView在所佔的空間
mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap);
mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap);
//標記detach要回收的view
detachAndScrapView(scrap, recycler);
updateWindowSizing();
int childLeft;
int childTop;
/*
* Reset the visible and scroll positions
*/
mFirstVisiblePosition = 0;
childLeft = childTop = 0;
//將所有子view交給recycler去執行緩存策略
detachAndScrapAttachedViews(recycler);
//Fill the grid for the initial layout of views
fillGrid(DIRECTION_NONE, childLeft, childTop, recycler);
}
在上面提到的dispatchLayoutStep1.2這兩個方法中都會調用到onLayoutChildren,然後調用detachAndScrapAttachedViews的關鍵方法.下面是源碼:
//遍歷子View 加入緩存
public void detachAndScrapAttachedViews(Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
//判斷子View該怎麼加入緩存
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
//如果不是setAdapter或者整體刷新,不是調用了remove方法或者View綁定內容無效
//則移除VIew放入到 mCachedViews,mRecyclerPool中
////stable id 標識一個viewholder的唯一性
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
//否則加入 mAttachedScrap,mChangedScrap緩存中
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
從上面的那個方法我們看到了Recycler根據不同的條件來加入不同的緩存集合中.接着具體的看一下,在scrapOrRecycleView方法中的removeViewAt(index) 之後是怎麼把ViewHolder放入對應的緩衝池裏面.
//viewHolder存放緩存策略
void recycleViewHolderInternal(ViewHolder holder) {
//省略拋出異常
//noinspection unchecked
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
if (DEBUG && mCachedViews.contains(holder)) {
throw new IllegalArgumentException("cached view received recycle internal? "
+ holder + exceptionLabel());
}
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
//
int cachedViewSize = mCachedViews.size();
//如果大於mViewCacheMax = 2
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
//將cachedViewIndex爲0的數據存入pool
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// 添加視圖時,跳過最近預取的視圖
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
//將holder加入緩存
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
//如果緩存cached失敗就加入pool.失敗的條件是 holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
// | ViewHolder.FLAG_REMOVED
// | ViewHolder.FLAG_UPDATE
// | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
// NOTE: 在動畫運行時滾動視圖時,視圖無法回收。 In this case, the item is eventually recycled by
// ItemAnimatorRestoreListener#onAnimationFinished.
// TODO: consider cancelling an animation when an item is removed scrollBy,
// to return it to the pool faster
if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are still removing it from animation lists"
+ exceptionLabel());
}
}
// even if the holder is not removed, we still call this method so that it is removed
// from view holder lists.
//即使沒有刪除holder,我們仍然會調用此方法,以便將其從view holder列表中刪除。
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
// 將index爲0的holder加入pool中,然後從cached中移除這個0
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
addViewHolderToRecycledViewPool(viewHolder);
mCachedViews.remove(cachedViewIndex);
}
我們從recycleViewHolderInternal方法可以得出以下結論:首先確認mCacheViews集合中是否還有空位,如果有空位,則直接放到mCacheViews集合,如果沒有的話就把mCacheViews集合中最前面的ViewHolder拿出來放到RecyclerViewPool中,然後再把最新的這個ViewHolder放到mCacheViews集合,如果沒有成功緩存到mCacheViews集合中,就直接放入RecyclerViewPool中.
接着看detachViewAt(index) 之後做了什麼.
/**
* 將依附的視圖標記爲剝離的狀態
*
* “Scrap”視圖依然附加在父級RecyclerView,可以重新綁定與重用。
* @param view View to scrap
*/
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
用一個例子來說明一下(例子來自揪克)我在這個大神的demo上變動了一些UI.
- 在mAttachedScrap會把屏幕上展示的內容緩存下來.mCachedViews默認大小爲2.
ViewHolder取出緩存的過程
在LinearLayoutManager裏面的fill方法的layoutChunk() 中我們看到了取出緩存的過程.
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
...
}
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
//取出緩存裏面的view
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
進入到RecyclerView裏面的方法:
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount()
+ exceptionLabel());
}
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
//預佈局之前,動畫相關
if (mState.isPreLayout()) {
//從mChangedScrap取出holder
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
//1) 根據位置到scrap/hidden list/cache找
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
//position如果匹配到了 ViewHolder,還判斷這個 ViewHolder 是否已經被 remove 與type的類型
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
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);
//如果recycler不是空的,那麼就是這個viewholder加入過了scrap中
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount() + exceptionLabel());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
//通過id查找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position更新位置
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = 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"
+ exceptionLabel());
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view." + exceptionLabel());
}
}
}
//通過緩存池裏面去取出
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
//這個type就是item的種類
holder = getRecycledViewPool().getRecycledView(type);
//找到了viewholder
if (holder != null) {
//重置內部的狀態,這樣就可以重新複用了
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
//--------------------如果pool裏面也沒有找到,那就重新創建ViewHolder---------------------
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline 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 (fromScrapOrHiddenOrCache && !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
+ exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//------------------------------------重新綁定viewholder------------------------------------------------
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
//設置LayoutParams
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
return holder;
}
從這個動圖來看,mCacheViews也印證了(recycleViewHolderInternal方法)上面說的存儲方式.在上下滑動的時候,會匹配id,直接取緩存,這樣就只會執行bindViewHolder了.
RecycledViewPool
public static class RecycledViewPool {
//viewType的默認緩存數量爲5.
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
//計數器
private int mAttachCount = 0;
public void clear() {
for (int i = 0; i < mScrap.size(); i++) {
ScrapData data = mScrap.valueAt(i);
data.mScrapHeap.clear();
}
}
//設置緩存數量的方法
public void setMaxRecycledViews(int viewType, int max) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mMaxScrap = max;
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
if (scrapHeap != null) {
//如果超過最大值,會remove掉相應ViewHolder。
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
}
public int getRecycledViewCount(int viewType) {
return getScrapDataForType(viewType).mScrapHeap.size();
}
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
//從緩存list的尾部逐個取出ViewHolder
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
int size() {
int count = 0;
for (int i = 0; i < mScrap.size(); i++) {
ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i).mScrapHeap;
if (viewHolders != null) {
count += viewHolders.size();
}
}
return count;
}
//
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
//重置緩存viewholder的屬性
scrap.resetInternal();
//加入緩存中
scrapHeap.add(scrap);
}
//省略代碼......
//RecyclerViewPool與一個adapter綁定會+1
void attach(Adapter adapter) {
mAttachCount++;
}
//RecyclerViewPool與一個adapter解綁會-1
void detach() {
mAttachCount--;
}
//綁定與解綁會回調此方法,同時計數
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
boolean compatibleWithPrevious) {
if (oldAdapter != null) {
detach();
}
if (!compatibleWithPrevious && mAttachCount == 0) {
//0的時候,就會清空這個RecyclerViewPool緩存池裏的所有ViewHolder
clear();
}
if (newAdapter != null) {
attach(newAdapter);
}
}
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
}
下面主要展示了緩存池內部的狀態.保存的viewHolder類型,以及在多type的item情況下,createViewHolder與BindVIewHolder
的顯示.
關於緩存池我並沒有看懂系統是怎麼樣的策略來把mCachedView裏面viewHolder賦值到RecycledViewPool中的.
目前我只是知道緩存池是用來處理不同itemViewType的緩存.避免多類型item重複創建對象.