本文基于 V7-25.3.1
圖文總結(jié)
RecyclerView優(yōu)點(diǎn)
- 更好的靈活配置能力劝术,在LayoutManager(布局)、Adapter(數(shù)據(jù)適配)和動畫的兼容上都更優(yōu)雅
- 緩存能力增強(qiáng)绳泉,離屏緩存相對較優(yōu),另增加了一層緩存池緩存
- 支持局部刷新拇勃,對于一些交互處理多的情況下,會帶來更好的性能
RecyclerView類圖
RecyclerView繪制流程圖
RecyclerView滑動流程圖
RecyclerView緩存介紹圖
源碼閱讀
RecyclerView使用方法分分析
RecyclerView 構(gòu)造方法
- 進(jìn)行View相關(guān)配置屬性設(shè)置,觸摸范圍妓肢、滑動速度等
- 設(shè)置Item動畫監(jiān)聽器
- 初始化AdapterManager方面,創(chuàng)建AdapterHelper(負(fù)責(zé)Adapter里的數(shù)據(jù)集發(fā)生變化時(shí)的預(yù)處理操作)
- 初始化ChildHelper(負(fù)責(zé)管理和訪問 RecyclerView 的子視圖)
- 如果配置了LayoutManager 則通過反射方法創(chuàng)建它
public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
...
// View配置相關(guān)屬性設(shè)置
final ViewConfiguration vc = ViewConfiguration.get(context);
mTouchSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
// 設(shè)置Item動畫監(jiān)聽器
mItemAnimator.setListener(mItemAnimatorListener);
// 設(shè)置 AdapterManager
initAdapterManager();
// 設(shè)置 ChildrenHelper
initChildrenHelper();
// 硬件加速相關(guān)屬性設(shè)置
if (ViewCompat.getImportantForAccessibility(this)
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
ViewCompat.setImportantForAccessibility(this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
mAccessibilityManager = (AccessibilityManager) getContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE);
setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
// 如果attrs指定了LayoutManager,則創(chuàng)建LayoutManager
boolean nestedScrollingEnabled = true;
if (attrs != null) {
int defStyleRes = 0;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
defStyle, defStyleRes);
String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);
int descendantFocusability = a.getInt(
R.styleable.RecyclerView_android_descendantFocusability, -1);
if (descendantFocusability == -1) {
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
}
a.recycle();
// 反射方法創(chuàng)建 LayoutManager
createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
if (Build.VERSION.SDK_INT >= 21) {
// SDK >=21下 孽尽,nestedScrollingEnabled狀態(tài)支持變更
a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,
defStyle, defStyleRes);
nestedScrollingEnabled = a.getBoolean(0, true);
a.recycle();
}
} else {
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
}
// 重置nestedScrollingEnabled狀態(tài) SDK 21以下默認(rèn)true
setNestedScrollingEnabled(nestedScrollingEnabled);
}
setLayoutManager
- 處理重新設(shè)置一個(gè)新的LayoutManager的一些邏輯
- 設(shè)置this給到LayoutManager祥款,并如果attach了則執(zhí)行LayoutManger的attach分發(fā)實(shí)踐
- 跟新緩存大小,并請求重新布局
public void setLayoutManager(LayoutManager layout) {
if (layout == mLayout) {
return;
}
stopScroll();
// 設(shè)置新的layout情況下的一些處理邏輯
...
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
// layout只能綁定一個(gè)mRecyclerView
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout +
" is already attached to a RecyclerView: " + layout.mRecyclerView);
}
// 設(shè)置this引用給LayoutManager
mLayout.setRecyclerView(this);
if (mIsAttached) {
// 分發(fā)attach事件
mLayout.dispatchAttachedToWindow(this);
}
}
// 重新更新緩存大小 及請求重新布局
mRecycler.updateViewCacheSize();
requestLayout();
}
setAdapter
- 接觸frozen狀態(tài)
- 設(shè)置新的Adapter,并觸發(fā)一系列監(jiān)聽事件
public void setAdapter(Adapter adapter) {
// 解除frozen狀態(tài)
setLayoutFrozen(false);
// 替換到當(dāng)前Adapter依痊,并觸發(fā)監(jiān)聽
setAdapterInternal(adapter, false, true);
requestLayout();
}
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
// 舊Adapter進(jìn)行解綁數(shù)據(jù)監(jiān)聽 和 RecyclerView的引用
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews(); // 移除緩存的View
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
// 處理新設(shè)置的Adapter的關(guān)聯(lián)監(jiān)聽和RecyclerView
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
// 通知LayoutManager Adapter變更
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
// 觸發(fā)Recycler Adapter變更事件
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
// 狀態(tài)置為 mStructureChanged
mState.mStructureChanged = true;
markKnownViewsInvalid();
}
RecyclerView繪制方法分析
onMeasure
- 未賦值layoutManager情況下凉逛,走默認(rèn)measure衔沼,結(jié)果是無展示
- 系統(tǒng)提供的LayoutManager默認(rèn)AutoMeasure。執(zhí)行LayoutManger的onMeasure方法
- 如果未指定確定寬高的尺寸規(guī)格,則會進(jìn)行布局酬荞,繼而獲得子View的大小混巧。此過程可能執(zhí)行兩次
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {// 無LayoutManger
defaultOnMeasure(widthSpec, heightSpec);
return;
}
// Android提供的三個(gè)LayoutManger,都是AutoMeasure
if (mLayout.mAutoMeasure) {
// 獲取測量規(guī)格
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
// 執(zhí)行LayoutManager的onMeasure方法
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
// 如果測量規(guī)格不確定 且設(shè)置了Adapter深员,則先執(zhí)行一次layout
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// 設(shè)置測量規(guī)格
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// 設(shè)置 獲取到子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.onLayout
- 執(zhí)行DispatchLayout方法
- 根據(jù)不同State狀態(tài),分別執(zhí)行Step1燎悍、Step2、Step3方法
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
void dispatchLayout() {
mState.mIsMeasuring = false;
// 如果State狀態(tài)是 State.STEP_START 則執(zhí)行 Step1 和Step2
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
// 直接執(zhí)行 step2
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
mLayout.setExactMeasureSpecsFrom(this);
}
// 執(zhí)行Step3 ,主要保存一些View信息和動畫執(zhí)行
dispatchLayoutStep3();
}
dispatchLayoutStep1
- 第一步layout方法
- 處理adapter變更
- 確定需要執(zhí)行的動畫
- 針對當(dāng)前的Views進(jìn)行信息緩存
- 如有必要鸽粉,則進(jìn)行預(yù)布局并緩存信息
private void dispatchLayoutStep1() {
// State狀態(tài)斷言
mState.assertLayoutStep(State.STEP_START);
mState.mIsMeasuring = false;
// 是否過濾掉 RequestLayout執(zhí)行玷或,需要過濾的時(shí)候 執(zhí)行該方法蔬胯,會使過濾次數(shù)+1
eatRequestLayout();
// 清楚 ViewInfo 所有狀態(tài)和其存在的數(shù)據(jù)
mViewInfoStore.clear();
// 執(zhí)行進(jìn)入 layout或者scroll行為標(biāo)志
onEnterLayoutOrScroll();
// 執(zhí)行Adapter變更及計(jì)算那些需要執(zhí)行的動畫
processAdapterUpdatesAndSetAnimationFlags();
// 存儲焦點(diǎn)信息
saveFocusInfo();
// state狀態(tài)信息設(shè)置
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
// 尋找 layout過程中position的最大和最小值
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
...
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
// 遍歷VieHolder
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
// 創(chuàng)建 ItemHolderInfo
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
// mViewInfoStore存儲 holder及其對應(yīng)animation信息
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
// 如果holder確定要更新舞竿,就把它添加到 oldChangeHolders 集合中
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
if (mState.mRunPredictiveAnimations) {
... // 運(yùn)行預(yù)布局
}
// 執(zhí)行退出 layout或者scroll行為標(biāo)志
onExitLayoutOrScroll();
// 對應(yīng) mEatRequestLayout -1
resumeRequestLayout(false);
// 狀態(tài)進(jìn)入 State.STEP_LAYOUT
mState.mLayoutStep = State.STEP_LAYOUT;
}
private void processAdapterUpdatesAndSetAnimationFlags() {
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess(); // 預(yù)處理
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
// 計(jì)算 mRunSimpleAnimations 和 mRunPredictiveAnimations
// mDataSetHasChangedAfterLayout 數(shù)據(jù)是否變化
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
dispatchLayoutStep2
- 執(zhí)行最終的View布局操作,該過程由LayoutManager完成
- 該方法可能會被多次執(zhí)行
private void dispatchLayoutStep2() {
// 過濾掉 RequestLayout執(zhí)行米愿,需要過濾的時(shí)候 執(zhí)行該方法育苟,會使過濾次數(shù)+1。對應(yīng)resumeRequestLayout方法進(jìn)行消費(fèi)
eatRequestLayout();
// 對應(yīng) onExitLayoutOrScroll
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
// 跳過預(yù)處理過程,一次性執(zhí)行完所有的update
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount(); // 賦值 itemCOunt
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
mState.mInPreLayout = false;
// 執(zhí)行 layout (執(zhí)行 LayoutManager 布局)
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
// State狀態(tài)進(jìn)入 State.STEP_ANIMATIONS
mState.mLayoutStep = State.STEP_ANIMATIONS;
// 對應(yīng) onExitLayoutOrScroll
onExitLayoutOrScroll();
// 對應(yīng)eatRequestLayout方法
resumeRequestLayout(false);
}
dispatchLayoutStep3
layout過程最后一步,執(zhí)行相關(guān)動畫和一些清理事項(xiàng)
private void dispatchLayoutStep3() {
...
if (mState.mRunSimpleAnimations) {
// 執(zhí)行相關(guān)動畫
...
}
// 一些清理動作
}
draw
主要涉及Item裝飾的繪制和動畫
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
boolean needsInvalidate = false;
...
if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 &&
mItemAnimator.isRunning()) {
needsInvalidate = true;
}
if (needsInvalidate) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
LinearLayoutManager 填充子View過程
LinearLayoutManager.onLayoutChildren
- Child布局執(zhí)行核心方法
- 布局方式万矾,通過確定錨點(diǎn),首先以錨點(diǎn)為基準(zhǔn)上到下布局慎框,在以錨點(diǎn)為基準(zhǔn)從下往上布局良狈。如果還有空間,繼續(xù)從上到下布局笨枯。最后確認(rèn)整個(gè)間隙是正確的薪丁。(反向布局及橫向反之則可)
- 該方法為LayoutManager布局核心執(zhí)行方法严嗜,Child的測量和添加工作在fill這個(gè)重要方法執(zhí)行哮塞,接下來會闡述
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// 確定是否需要反向布局
// 確定錨點(diǎn)及偏移量 (1. 優(yōu)先焦點(diǎn)child 2. 如果是反向布局缓醋,則找recycler里面最最接近尾部的child 3. 如果是正向,則找最接近頭部的child)
// 計(jì)算額外的偏移量(RecyclerView padding)
...
// 錨點(diǎn)準(zhǔn)備ready
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
// 臨時(shí) detach和回收當(dāng)前的view 第一次 measure 的時(shí)候不會產(chǎn)生效果胰蝠,因?yàn)榇藭r(shí) RecyclerView 還沒有子 View梗搅。 而在第二第三次 layout 時(shí),它會把子 View 從 RecyclerView 中 remove 或 detach 辱士,并緩存子 View鼠证,以便之后重新 add 回來或 attach 回來,避免重復(fù)加載相同的子 View
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
// 開始填充view
if (mAnchorInfo.mLayoutFromEnd) {
... // 反向填充
} else { // 正向填充
// (基于錨點(diǎn)位置先 由上到下||由左到右)更新錨點(diǎn)信息
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd; // 額外的尾部偏移量
// 開始填充 View布局主要方法
fill(recycler, mLayoutState, state, false);
// 尾部位移
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// (基于錨點(diǎn)位置 由下到上||由右到左)更新錨點(diǎn)信息
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
// 二次填充
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
// 仍有可用空間
if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
// 繼續(xù) (基于錨點(diǎn)位置先 由上到下||由左到右)更新信息并填充View
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
// 有滑動位置導(dǎo)致的gap間隙修復(fù)處理
...
// 預(yù)布局動畫處理
...
}
LinearLayoutManager.fill
- 如果是滑動流程,則根據(jù)情況進(jìn)行回收流程
- LayoutState中部分成員變量含義米绕,mOffset:填充起始坐標(biāo)窿给,mCurrentPosition:填充起始數(shù)據(jù)的position靴寂,mAvailable:本次滑動可填充的距離,mScrollingOffset:滑動過的總量循環(huán)依次加載子View
- 確定可布局大小缴守,直至布局大小消費(fèi)完成
- 加載子View在 layoutChunk 中執(zhí)行
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// 可布局的位移
final int start = layoutState.mAvailable;
// 滑動偏移的情況下
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 執(zhí)行回收流程
recycleByLayoutState(recycler, layoutState);
}
// 余量大小
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
// 每次布局結(jié)果中間記錄 方便運(yùn)算
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
// 加載子View
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
// 計(jì)算布局使用過的大小值
if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
remainingSpace -= layoutChunkResult.mConsumed;
}
// 如果當(dāng)前正在滾動屏幕
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 把移出屏幕的 View 緩存到 mCachedViews 里面
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
return start - layoutState.mAvailable;
}
LinearLayoutManager.layoutChunk
- 通過layoutState.next(recycler)獲取目標(biāo)布局View
- 獲取目標(biāo)View完畢后歇僧,進(jìn)行含裝飾的Margin計(jì)算苦酱,并執(zhí)行布局
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 獲取下一個(gè)布局View (核心方法)
View view = layoutState.next(recycler);
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) { // 除非特殊指定,否則mScrapList為null
// 執(zhí)行 addView
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
// 添加到末尾
addView(view);
} else {
addView(view, 0); // 添加到第一個(gè)位置
}
} else {
...
}
measureChildWithMargins(view, 0, 0); // 測量子View的Margins
// 計(jì)算 含裝飾的Margin值的大小
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
// 計(jì)算 r、l、t略吨、b的值
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
...
}
// 對View進(jìn)行布局
layoutDecoratedWithMargins(view, left, top, right, bottom);
// 部分狀態(tài)改變
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
LinearLayoutManager.next
通過RecyclerView.Recycler獲取對應(yīng)Pos的View
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) { // 除非定制View,不然為null
return nextViewFromScrapList();
}
通過RecyclerView.Recycler 獲取目標(biāo)position對應(yīng)的View
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection; // 當(dāng)前pos 增加
return view;
}
Recycler獲取VH的緩存和創(chuàng)建過程
Recycler.getViewForPosition
根據(jù)Pos獲取View方法匣砖,最終執(zhí)行tryGetViewHolderForPositionByDeadline獲取View
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
Recycler.tryGetViewHolderForPositionByDeadline
- 獲取ViewHolder方法
- 如果是預(yù)布局,線通過ChangeScrap中獲取
- 第一次嘗試獲取VH,依次從Scrap、Hidden登澜、Cache中獲取VH
- 第二次嘗試獲取VH晃跺,針對具有StableId的Adapter莲趣,根據(jù)id依次從Scrap和Cache獲取
- 第三次嘗試從自定義緩存中獲取VH
- 第四次嘗試從Recycler獲取VH
- 最后直接創(chuàng)建VH
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) 如果是預(yù)布局溉仑, 從mChangedScrap中獲取
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) 第一次嘗試獲取挖函,依次從Scrap、Hidden浊竟、Cache中獲取VH
if (holder == null) {
// 依次從Scrap怨喘、Hidden津畸、Cache中獲取VH
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
...
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) 第二次嘗試獲取,當(dāng)Adapter具備StableIds情況
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
// 3) 第三次嘗試從 自定義緩存獲取
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
...
}
}
// 4) 第四次嘗試 從 RecyclerPool中獲取
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
...
}
// 5) 開始創(chuàng)建
if (holder == null) {
long start = getNanoTime();
// 創(chuàng)建VH
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// 為bind過必怜,執(zhí)行bind方法
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
}
private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
...
// 執(zhí)行Adapter bindViewHolder方法
mAdapter.bindViewHolder(holder, offsetPosition);
...
return true;
}
00 從ChangeScrap獲取
針對的是預(yù)布局狀態(tài)肉拓,從mChangedScrap中獲取目標(biāo)ViewHolder
ScrapView:View仍然attach在其父RecyclerView上且可以被重復(fù)綁定數(shù)據(jù)及重復(fù)使用。將View標(biāo)記為Scrap過程中分為兩大類mAttachedScrap 和 mChangedScrap梳庆。
mAttachedScrap:VH有ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID這兩個(gè)Flag暖途,或者VH是沒有被更新過的,或者是可以被重新更新的VH靠益。
其它則是mChangedScrap
ViewHolder getChangedScrapViewForPosition(int position) {
// 必須是預(yù)布局狀態(tài)丧肴,取mChangedScrap中的ViewHolder
final int changedScrapSize;
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// 通過position獲取
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// 如果Adapter是固定id残揉,嘗試從Adapter獲取
if (mAdapter.hasStableIds()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
// Mark an attached view as scrap.
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
第一次嘗試獲取VH(AttachScrap胧后、Hidden、CacheView)
- 先從 mAttachedScrap中獲取VH
- 從隱藏且未移出的View中獲取 View
- 從一級緩存CacheView中獲取
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// 先從 mAttachedScrap中獲取VH
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
// 驗(yàn)證VH是否可用抱环,若可用壳快,則直接返回該VH
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// dryRun 傳遞是false(代表VH在scrap、cache中可以被Removed)
if (!dryRun) {
// 從隱藏且未移出的View中獲取 View
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
// View可用镇草,則進(jìn)行可視眶痰、detach、scrap緩存
final ViewHolder vh = getChildViewHolderInt(view);
mChildHelper.unhide(view);
int layoutIndex = mChildHelper.indexOfChild(view);
mChildHelper.detachViewFromParent(layoutIndex);
scrapView(view);
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
// 從第一級緩存View中獲取
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
// VH是有效的
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
mCachedViews.remove(i); // 移出獲取的cache
}
return holder; // 返回VH
}
}
return null;
}
第二次嘗試獲取VH(Adapter有穩(wěn)定id情況)
- Adapter配置的id是穩(wěn)定的梯啤,穩(wěn)定指數(shù)據(jù)集變化的時(shí)候竖伯,對于同一數(shù)據(jù)對應(yīng)的id是唯一的
- 先嘗試從Scrap獲取VH,非dryRun下因宇,將未命中的從Scrap中移出七婴,并加入到Cache或Pool緩存
- 在嘗試從Cache獲取VH,將未命中的從Cache中移出察滑,并加入到Pool緩存
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// 從AttachedScrap中 嘗試獲取VH
final int count = mAttachedScrap.size();
for (int i = count - 1; i >= 0; i--) {
final ViewHolder holder = mAttachedScrap.get(i);
// id 相等 且 holder 非Scrap返回
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
if (type == holder.getItemViewType()) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
if (holder.isRemoved()) {
// 從事
if (!mState.isPreLayout()) {
holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
}
}
return holder;
} else if (!dryRun) {
// 從AttachedScrap移除
mAttachedScrap.remove(i);
removeDetachedView(holder.itemView, false);
// 回收加入至 cache 或者 pool
quickRecycleScrapView(holder.itemView);
}
}
}
// 從CacheView中嘗試獲取
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder.getItemId() == id) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
mCachedViews.remove(i); // 從Cache中移出
}
return holder;
} else if (!dryRun) {
// 從Cache中移出打厘,放到pool中
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
RecyclerView滑動機(jī)制分析
根據(jù)View事件機(jī)制可以直接來看onTouchEvent方法。
重點(diǎn)查看move事件贺辰。move事件執(zhí)行了scrollByInternal方法户盯。該方法最后會執(zhí)行LayoutManager的Scroll方法,以LinearLayoutManager為例饲化,它的ScrollBy方法最終執(zhí)行到fill方法莽鸭。也就是上文提到的ItemView填充方法,滑動過程中會不斷執(zhí)行獲取對應(yīng)位置的ViewHolder吃靠,然后進(jìn)行View的展示硫眨。從而實(shí)現(xiàn)RecyclerView的滑動
public boolean onTouchEvent(MotionEvent e) {
...
switch (action) {
case MotionEvent.ACTION_DOWN: {
...
case MotionEventCompat.ACTION_POINTER_DOWN:
...
case MotionEvent.ACTION_MOVE: { // 觸摸時(shí)間-move
...
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
// 執(zhí)行內(nèi)部滑動方法
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
... }
} break;
case MotionEventCompat.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: { // 觸摸事件-up
// 執(zhí)行 fling方法 ,主要做一些item和scroller動畫等操作
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
} break;
}
...
return true;
}
scrollByInternal
- 內(nèi)部Scroll執(zhí)行方法撩笆,此處會執(zhí)行LayoutManager的Scroll方法
- 其它處罰Nested捺球、OnScroll等事件
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0, unconsumedY = 0;
int consumedX = 0, consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
eatRequestLayout();
onEnterLayoutOrScroll();
TraceCompat.beginSection(TRACE_SCROLL_TAG);
if (x != 0) {
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
}
if (y != 0) {
// LinearLayout 豎向布局為例缸浦,走LayoutManager滑動放啊放
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
TraceCompat.endSection();
repositionShadowingViews();
onExitLayoutOrScroll();
resumeRequestLayout(false);
}
if (!mItemDecorations.isEmpty()) {
invalidate();
}
// 分發(fā) NestedScroll事件
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
...
}
if (consumedX != 0 || consumedY != 0) {
dispatchOnScrolled(consumedX, consumedY); // 分發(fā)onScrolled事件
}
if (!awakenScrollBars()) {
invalidate();
}
return consumedX != 0 || consumedY != 0;
}
LinearLayoutManager執(zhí)行滑動處理
- 執(zhí)行scrollBy方法
- scrollBy方法最終走到 fill方法(上面提到的填充子View方法)
- 該方法則會進(jìn)行 ItemView的填充。從而完成Recycler滑動時(shí)氮兵,View的重新創(chuàng)建或者重新綁定一系列過程
- 平移整個(gè)View的child裂逐,實(shí)現(xiàn)滑動效果
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
...
mLayoutState.mRecycle = true;
ensureLayoutState();
final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(dy);
// 更新LayoutState,布局方向和偏移值泣栈。目的是讓LayoutManager知道從開始還是末尾進(jìn)行回收和填充
updateLayoutState(layoutDirection, absDy, true, state);
// 執(zhí)行 LinearLayout的fill 方法
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
...
// 平移整個(gè)view的child
mOrientationHelper.offsetChildren(-scrolled);
return scrolled;
}
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
// 執(zhí)行回收流程
recycleByLayoutState(recycler, layoutState);
...
// 執(zhí)行填充流程(參考上面layoutChunk方法)
layoutChunk(recycler, state, layoutState,layoutChunkResult);
}
LinearLayoutManager回收流程
- 根據(jù)不同的布局方向進(jìn)行不同方向的回收卜高。以Start為例介紹
- 計(jì)算位移limit值,根據(jù)limit
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
// 假設(shè)是 初始方向布局南片,則開始末尾View回收掺涛。反之亦然
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
} else {
recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
}
}
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
final int limit = dt;
final int childCount = getChildCount();
if (mShouldReverseLayout) {
...
} else {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 遍歷child,當(dāng)超過限制大小時(shí)候疼进,開始回收
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
recycleChildren(recycler, 0, i); // 執(zhí)行Children回收流程
return;
}
} }
}
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
if (endIndex > startIndex) {
for (int i = endIndex - 1; i >= startIndex; i--) {
removeAndRecycleViewAt(i, recycler); // 執(zhí)行RecyclerView的移出和回收方法
}
} else {
for (int i = startIndex; i > endIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
}
}
RecyclerView.removeAndRecycleViewAt
- 移出和回收View方法
- 執(zhí)行ChildHelper的移出View方法薪缆。內(nèi)部Bucket移出和回掉CallBack進(jìn)行View移出
- 執(zhí)行Recycler回收方法
public void removeAndRecycleViewAt(int index, Recycler recycler) {
final View view = getChildAt(index); // 獲取目標(biāo)View
removeViewAt(index); // 執(zhí)行ChildHelper移出
recycler.recycleView(view); // 回收View
}
public void removeViewAt(int index) {
final View child = getChildAt(index);
if (child != null) {
mChildHelper.removeViewAt(index); // 執(zhí)行ChildHelper移出
}
}
public void recycleView(View view) {
ViewHolder holder = getChildViewHolderInt(view); // 獲取VH
// ViewHolder 回收前,需要完全detach伞广、且不是Scrap
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
holder.unScrap();
} else if (holder.wasReturnedFromScrap()){
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder); // 執(zhí)行回收
}
RecyclerView.recycleViewHolderInternal
- 內(nèi)部緩存VH方法
- 如果CacheView滿了拣帽,則移出一個(gè)Cache到Pool中
- 將目標(biāo)VH緩存到Cache末尾
- 如果沒有Cache成功,則直接緩存到Pool中
void recycleViewHolderInternal(ViewHolder holder) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Cache緩存?zhèn)€數(shù)超了嚼锄,則直接回收CacheView到RecyclerPool
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
// 將VH緩存到CacheView中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
// 如果未CacheView緩存减拭,則直接緩存RecyclerViewPool中
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
...
}
局部刷新
Adapter數(shù)據(jù)操作對外API
RecyclerView.Adapter提供局部數(shù)據(jù)變化通知方法,然后執(zhí)行到RecyclerViewDataObserver對應(yīng)的各種數(shù)據(jù)操作方法上区丑。
RecyclerViewDataObserver
- 通過mAdapterHelper進(jìn)行數(shù)據(jù)變化處理操作
- 然后觸發(fā)更新處理
- 下面介紹下 ItemChanged操作
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
triggerUpdateProcessor();
}
}
public void onItemRangeInserted(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
AdapterHelper.onItemRangeChanged
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
// 添加一個(gè)更新操作 拧粪,標(biāo)志為update、記錄pos沧侥、item相關(guān)信息
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
mExistingUpdateTypes |= UpdateOp.UPDATE;
// 如果只有一個(gè)待處理操作則為true可霎,true則執(zhí)行后續(xù)更新處理。如果是多個(gè)正什,則會忽略啥纸,因?yàn)樵诘谝淮纬霭l(fā)后,就會集中處理
return mPendingUpdates.size() == 1;
}
RecyclerViewDataObserver.triggerUpdateProcessor
- 當(dāng)RecyclerView有固定大小婴氮,且已經(jīng)Attached了斯棒。則走Runnable更新
- 否則直接走requestLayout方式更新,即重新走繪制流程 onMeasure主经、onLayout等
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
// RecyclerView有固定大小的時(shí)候 會執(zhí)行mUpdateChildViewsRunnable 來處理更新
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
// 直接走 requestLayout方式來處理
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
triggerUpdateProcessor下requestLayout
requestLayout下 onMeasure -> dispatchLayout -> dispatchLayoutStep2 -> layoutChildren -> fill -> layoutChunk -> next -> tryGetViewHolderForPositionByDeadline
最終對Item進(jìn)行重新綁定 實(shí)現(xiàn)局部刷新邏輯
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// 執(zhí)行數(shù)據(jù)變化的Holder的重新bind荣暮,從而實(shí)現(xiàn)局部刷新
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
}
triggerUpdateProcessor下mUpdateChildViewsRunnable
當(dāng)RecyclerView有固定大小時(shí),則不需要Measure罩驻,直接走dispatchLayout方法進(jìn)行刷新操作
final Runnable mUpdateChildViewsRunnable = new Runnable() {
@Override
public void run() {
...
// 消費(fèi) 等待執(zhí)行的操作
consumePendingUpdateOperations();
}
}
void consumePendingUpdateOperations() {
if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
.hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
| AdapterHelper.UpdateOp.MOVE)) {
// update 情況下 邏輯
eatRequestLayout();
onEnterLayoutOrScroll();
// 數(shù)據(jù)預(yù)處理
mAdapterHelper.preProcess();
if (!mLayoutRequestEaten) {
// 執(zhí)行 dispatchLayout 進(jìn)行局部刷新處理
if (hasUpdatedView()) {
dispatchLayout();
} else {
// no need to layout, clean state
mAdapterHelper.consumePostponedUpdates();
}
}
...
} else if (mAdapterHelper.hasPendingUpdates()) {
// add穗酥、remove等操作,直接執(zhí)行dispatchLayout
dispatchLayout();
TraceCompat.endSection();
}
}