本片文章來分析RecyclerView數據刷新,你將會看到緩存在數據刷新過程當中的使用,以及不一樣的數據刷新方法對性能的影響。java
閱讀本文前,你須要前面的兩篇文章的基礎數組
爲RecyclerView
設置Adapter
的時候,會給Adapter
設置一個數據觀察者RecyclerViewDataObserver mObserver
。在Adapter
通知數據更新的時候,這個觀察者會根據狀況完成界面刷新工做。緩存
首先來分析最簡單粗暴,並且也是最經常使用的數據刷新方法,Adapter#notifyDataSetChanged()
,它表示數據徹底改變,界面須要全局刷新。app
Adapter#notifyDataSetChanged()
會調用RecyclerViewDataObserver#onChanged()
方法ide
private class RecyclerViewDataObserver extends AdapterDataObserver {
@Override
public void onChanged() {
// 表示數據的結構已經徹底改變
mState.mStructureChanged = true;
// 1.預處理工做,響應數據集改變
processDataSetCompletelyChanged(true);
// 2.若是沒有等待更新的操做,那麼就當即請求從新佈局
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
}
複製代碼
RecyclerViewDataObserver
接收到數據徹底改變的消息後,它首先作一些預處理工做,以響應數據集改變,而後請求從新佈局來刷新界面。工具
首先來看看數據集改變的預處理工做到底作了啥佈局
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
// 參數傳入的值爲true,代表須要分發數據改變的事件
mDispatchItemsChangedEvent |= dispatchItemsChanged;
// 代表在layout後,數據集徹底改變了
mDataSetHasChangedAfterLayout = true;
// 標記已知的View爲無效的
markKnownViewsInvalid();
}
複製代碼
processDataSetCompletelyChanged()
首先作了一些狀態標記,而後調用markKnownViewsInvalid()
來標記已知的View爲無效的。post
void markKnownViewsInvalid() {
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore()) {
// 1. 遍歷RecyclerView全部子View,設置FLAG_UPDATE和FLAG_INVALID標籤
holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
}
}
// 2. 標記ItemDecoration區域爲dirty
markItemDecorInsetsDirty();
// 3. 對mCachedViews中保存的View,設置FLAG_UPDATE和FLAG_INVALID標籤,甚至用RecyclerPool回收這些View
mRecycler.markKnownViewsInvalid();
}
複製代碼
markKnownViewsInvalid()
處理的目標不只僅只有RecyclerView
的子View,並且還包括mCachedViews
緩存中的View。性能
界面因爲滑動,致使某些不可見的子View被移除,而且優先使用
mCachedViews
緩存它。當再次因爲滑動須要顯示這個子View時,就會從mCachedViews
中獲取。優化
markKnownViewsInvalid()
作了兩件事
FLAG_UPDATE
和FLAG_INVALID
。ItemDecoration
區域爲dirty
,也就是設置View的佈局參數的mInsetsDirty
值爲true
,表示須要刷新View的ItemDecoration
區域。預處理工做作完了,就會調用requestLayout()
來從新佈局,咱們把主要精力放在layout
過程。
layout
過程分爲了三步,dispatchLayoutStep1()
處理更新操做以及保存動畫信息,dispatchLayoutStep3()
執行動畫並作一些清理工做,而dispatchLayoutStep2()
是完成了數據刷新的工做。
dispatchLayoutStep2()
把子View的佈局工做交給了LayoutManager#onLayoutChildren()
完成,這裏以LinearLayoutManager#onLayoutChidren()
爲例進行分析。
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// ...
// 1. 分離/廢棄並緩存子View
detachAndScrapAttachedViews(recycler);
// 2. 填充子View,由layoutChunk()實現
fill(recycler, mLayoutState, state, false);
// ...
}
複製代碼
LinearLayoutManager
對子View的佈局工做大體分爲兩步。首先是分離/移除子View,並緩存它。而後是獲取子View並填充給RecyclerView
。
首先咱們來分析分離/移除和緩存這一步,調用的是LayoutManager#detachAndScrapAttachedViews()
方法,它會遍歷RecyclerView
全部子View,而後調用LayoutManager#scrapOrRecycleView()
方法來執行分離/移除和緩存子View
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
return;
}
// 如下條件是知足的
// 子View的ViewHolder設置過FLAG_INVALID
// 子View的ViewHolder沒有設置過FLAG_REMOVED
// Adapter默認沒有開啓stable id功能
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// 1. 從RecyclerView中移除子View
removeViewAt(index);
// 2. Recycler回收子View
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
複製代碼
在預處理工做環節,把View標記爲FLAG_INVALID
,所以就會先把這個View從RecyclerView
中移除,使用的是ViewGroup#removeViewAt()
方法。而後使用Recycler
來回收這些被移除的子View。
咱們如今來看下回收的過程
void recycleViewHolderInternal(ViewHolder holder) {
// ...
if (forceRecycle || holder.isRecyclable()) {
// 因爲View被標記爲FLAG_INVALID,因此沒法使用mCachedViews這個緩存
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// ...
}
// 不能使用mCachedViews這個緩存,就交給RecyclerPool回收
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
}
// ...
}
複製代碼
因爲子View被標記爲FLAG_INVALID
,所以子View只能交給RecyclerPool
進行回收。
如今全部子View都被RecyclerPool
回收了,那麼接下來分析如何給RecyclerView
填充子View。這一步是由LinearLayoutManager#layoutChunk()
實現的
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
// ...
// 1. 從Recycler中獲取View
View view = layoutState.next(recycler);
// 2. 把子View添加到RecyclerView中
addView(view);
// 3. 測量子View
measureChildWithMargins(view, 0, 0);
// 4. 佈局子View
layoutDecoratedWithMargins(view, left, top, right, bottom);
// ...
}
複製代碼
填充子View經歷了這四步,可是咱們把目光放在如何從Recycler
中獲取View。它是由Recycler#tryGetViewHolderForPositionByDeadline()
實現的
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 1. 首先從mChangedScrap中獲取
// LinearLayoutManager在大部分狀況下是支持predictive item animations
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 2. 從mAttachedScrap, hidden view, mCachedViews中獲取
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
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);
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);
final int type = mAdapter.getItemViewType(offsetPosition);
// 3. 經過stable id從mAttachedScrap, mCachedViews中獲取
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) {
// 4. 從自定義緩存中獲取
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
if (holder == null) { // fallback to pool
// 5. 從RecyclerPool中獲取
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
// 重置全部的標誌位
holder.resetInternal();
// ...
}
}
if (holder == null) {
// 6. 利用Adapter建立
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
// ...
// 7. 根據條件決定是否執行綁定操做
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// 若是已經綁定,就只更新predictive item animations的位置信息
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// 若是沒有綁定,就須要執行綁定操做
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
// 8. 校訂佈局參數,並更新它
// ...
return holder;
}
複製代碼
這裏我把全部的獲取路徑都標註了,可是因爲View被標記爲FLAG_INVALID
,因此只能從RecyclerPool
進行獲取(第6步)。而從RecylerPool
獲取ViewHolder
後,它的全部標誌位都被重置了,所以還須要進行綁定(第7步)。
獲取到了View,就把它添加到RecyclerView
中,而後測量、佈局、繪製,所以完成了界面刷新過程。
如今咱們來總結下Adapter#notifyDataSetChanged()
方法的優缺點。
優勢就是簡單無腦。缺點就是影響繪製性能,由於它要把全部子View移除、回收、獲取、再綁定。
而在實際中,數據每每只改變一小部分,例如某幾項數據更新了,某幾項數據刪除了等等。這個時候咱們但願只刷新受影響的子View便可,而不是指望全部子View都刷新。
Adapter
提供了不少局部刷新的方法,例如notifyItemChanged()
用來處理數據更新,notifyItemInserted()
處理數據增長,notifyItemRemoved()
處理數據移除,notifyItemMove()
處理數據移動,而且還提供了相應的範圍操做的方法notifyRangXXX()
。這樣咱們就沒必要無腦使用notifyDataSetChanged()
方法,可是須要咱們本身比較數據,而後決定調用那種佈局刷新的方法。
全局刷新影響繪製性能,那麼咱們來看看局部刷新是如何優化繪製性能的。
咱們挑選Adapter#notifyItemChanged()
方法來分析,它會調用觀察者的onItemRangeChanged()
方法
private class RecyclerViewDataObserver extends AdapterDataObserver {
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
// 1. 通知AdapterHelper,某個範圍內數據有更新
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
// 2. 觸發更新
triggerUpdateProcessor();
}
}
}
複製代碼
首先會通知AdapterHelper
,某個範圍的數據有改變
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if (itemCount < 1) {
return false;
}
// 保存UPDATE操做
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
// 保存操做類型
mExistingUpdateTypes |= UpdateOp.UPDATE;
// 當只有一個待處理的更新操做時,表示須要當即處理
return mPendingUpdates.size() == 1;
}
複製代碼
AdapterHelper
會建立一個相應操做類型的UpdateOp
對象保存,而後也會保存這次操做的類型。
從返回值能夠看出,若是等待更新的操做只有一個,就表明須要理解處理。咱們假設如今只有一個更新操做,那麼會調用triggerUpdateProcessor()
來處理
void triggerUpdateProcessor() {
// POST_UPDATES_ON_ANIMATION在sdk大於16的時候爲true
// mHasFixedSize表示是否有固定尺寸
// mIsAttached表示是否RecyclerView是否添加到Window中
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
複製代碼
觸發更新操做受條件限制,這裏的限制條件基本上只有mHasFixedSize
,它是經過RecyclerView#setHasFixedSize()
設置的。若是RecyclerView
的寬高設置爲固定尺寸,例如100dp
,或者match_parent
,那麼能夠調用setHasFixedSize(true)
設置RecyclerView
有固定寬高。這樣能夠在某些時候避免RecyclerView
自我測量這一步。
可是不管使用哪一種方式執行更新操做,都會經歷layout過程。在dispatchLayoutStep1()
中會調用processAdapterUpdatesAndSetAnimationFlags()
處理這些更新操做,而且決定是否執行動畫。
本文不分析動畫部分的源碼。
private void processAdapterUpdatesAndSetAnimationFlags() {
// LinearLayoutManager若是不是在狀態恢復中,是支持可預測動畫特性的
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
// 省略動畫相關的代碼...
}
複製代碼
這裏又根據LayoutManager
是否支持Predictive item animations
,份內了兩種處理方式,可是它們異曲同工,最終它們都會處理受影響的子View。
Predictive item animations
: 對於添加,移除,移動操做(不包括改變操做),會自動建立一個動畫,這個動畫會顯示View
從哪裏來,到哪裏去。
對於數據改變操做,processAdapterUpdatesAndSetAnimationFlags()
最終會經過AdapterHelper
的以下代碼來處理受影響的子View
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
複製代碼
這個mCallback
是在RecyclerView
中實現的
void initAdapterManager() {
mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
// 處理範圍數據改變操做
viewRangeUpdate(positionStart, itemCount, payload);
// 表示是數據改變操做
mItemsChanged = true;
}
}
}
複製代碼
viewRangeUpdate()
處理了範圍數據改變的操做,比較簡單,總結以下
RecyclerView
處於數據改變範圍內的子View,被標記爲FLAG_UPDATE
,而且標記它的ItemDecoration
區域爲dirty
。mCachedViews
中緩存的,且處於數據改變範圍內View,被標記爲FLAG_UPDATE
,而且被RecyclerPool
回收。如今,dispatchLayoutStep1()
已經把受數據改變影響的View(包括mCachedView
緩存的)所有標記爲FLAG_UPDATE
,而後在dispatchLayoutStep2()
中爲子View進行從新佈局,它是由LayoutManager#onLayoutChildren()
實現的,咱們這裏仍然以LinearLayoutManager#onLayoutChildren()
爲例進行分析。
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// ...
// 1. 分離/廢棄並緩存子View
detachAndScrapAttachedViews(recycler);
// 2. 填充子View,由layoutChunk()實現
fill(recycler, mLayoutState, state, false);
// ...
}
複製代碼
這段代碼是否是似曾相識,沒錯,咱們剛在前面分析過,只不過此次分析的狀況是局部數據改變,而非全局刷新。
首先咱們來看下如何分離/廢棄並緩存子View的。
RecyclerView#detachAndScrapAttachedViews()
會遍歷全部子View,而後經過ReyclerView#scrapOrRecycleView()
來處理
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
return;
}
// 如今的狀況是View只被標記爲FLAG_UPDATE
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
} else {
// 1. 從RecyclerView分離子View
detachViewAt(index);
// 2. 緩存被分離的子View
recycler.scrapView(view);
// 爲動畫保存信息
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
複製代碼
如今的狀況是View只被標記爲FLAG_UPDATE
,這與全局刷新的狀況不同了,這裏第一步是把子View從RecyclerView
中分離(detach
)而不是移除(remove
)。它經過RecyclerView
中的以下回調實現
private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
public void detachViewFromParent(int offset) {
final View view = getChildAt(offset);
if (view != null) {
final ViewHolder vh = getChildViewHolderInt(view);
if (vh != null) {
// 1.添加FLAG_TMP_DETACHED標誌位
vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
}
}
// 2.調用ViewGroup#detachViewFromParent()分離子View
RecyclerView.this.detachViewFromParent(offset);
}
}
}
複製代碼
首先把子View標記爲FLAG_TMP_DETACHED
,而後分離子View。
移除(
remove
)和分離(detach
)有何區別?
- 移除會致使從新佈局,也就是
requestLayout()
。- 分離只是從
ViewGroup#mChildren
數組中移除引用,可是必須在同一個繪製週期內,把分離的View從新附着上去或者刪除。所以並不會引起從新佈局。
如今全部的子View都已經從RecyclerView
中分離了,接下來就會使用Recycler
來緩存它們,調用的是RecyclerView#scrapView()
方法
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
// 根據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);
}
}
複製代碼
在RecyclerView
的全部子View中,對於數據改變範圍內的子View,會被標記爲FLAG_UPDATE
,它會被mChangedScrap
緩存,而其餘子View會被mAttachedScrap
緩存。
如今咱們已經瞭解了局部刷新的緩存是如何使用的,那麼如今咱們來看看LinearLayoutManager
是如何實現子View填充的,它是由LinearLayoutManager#layoutChunk()
實現的
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
// ...
// 1. 從Recycler中獲取View
View view = layoutState.next(recycler);
// 2. 把子View添加/附着到RecyclerView中
addView(view);
// 3. 測量子View
measureChildWithMargins(view, 0, 0);
// 4. 佈局子View
layoutDecoratedWithMargins(view, left, top, right, bottom);
// ...
}
複製代碼
又是一段熟悉的代碼,咱們仍是把目光聚焦到如何從Recycler
獲取子View的過程,它是經過Recycler#tryGetViewHolderForPositionByDeadline()
實現的
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 1. 首先從mChangedScrap中獲取
// LinearLayoutManager在大部分狀況下是支持predictive item animations
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 2. 從mAttachedScrap, hidden view, mCachedViews中獲取
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
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);
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);
final int type = mAdapter.getItemViewType(offsetPosition);
// 3. 經過stable id從mAttachedScrap, mCachedViews中獲取
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) {
// 4. 從自定義緩存中獲取
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
if (holder == null) { // fallback to pool
// 5. 從RecyclerPool中獲取
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
// 重置全部的標誌位
holder.resetInternal();
// ...
}
}
if (holder == null) {
// 6. 利用Adapter建立
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
// ...
// 7. 根據條件決定是否執行綁定操做
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// 若是已經綁定,就只更新predictive item animations的位置信息
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// 若是沒有綁定,就須要執行綁定操做
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
// 8. 校訂佈局參數,並更新它
// ...
return holder;
}
複製代碼
對於部分數據改變操做,被分離的子View,只被標記爲FLAG_UPDATE
和FLAG_TMP_DETACHED
,所以能用到的獲取View的途徑只有第1步和第2步,也就是從mChangedScrap
和mAttachedScrap
中獲取。然而,對於其餘的操做,例如添加,移除,移動,可能就會從不一樣路徑獲取View。
對於數據改變這種狀況,從mChangedScrap
和mAttachedScrap
中獲取ViewHolder
的條件基本上只要知足一個條件便可,這個條件是從ViewHolder
獲取的位置要與填充的位置相等。
從mChangedScrap
和mAttachedScrap
中獲取ViewHolder
後,它仍是已經綁定的狀態。可是對於數據改變受影響的子View,因爲被標記爲FALG_UPDATE
,所以還須要再綁定一次數據,這樣就能夠達到數據更新的效果。而對於那些沒有受數據改變影響的子View,就不須要再綁定。
如今咱們已經從Recycler
中獲取了View,而且數據改變的View已經從新綁定數據,如今須要把這些分離的子View從新附着(attach
)到RecyclerView
上,它是經過LayoutManager#addViewInt()
實現的
private void addViewInt(View child, int index, boolean disappearing) {
final ViewHolder holder = getChildViewHolderInt(child);
// ...
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (holder.wasReturnedFromScrap() || holder.isScrap()) {
if (holder.isScrap()) {
holder.unScrap();
} else {
holder.clearReturnedFromScrapFlag();
}
// 處理分離子View的狀況,它會把子View從新attach到RecyclerView中
mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
if (DISPATCH_TEMP_DETACH) {
ViewCompat.dispatchFinishTemporaryDetach(child);
}
} else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
// 處理子View移動的狀況
} else {
// 對於其餘的狀況,都是把子View添加到RecyclerView中
mChildHelper.addView(child, index, false);
lp.mInsetsDirty = true;
if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
mSmoothScroller.onChildAttachedToWindow(child);
}
}
// ...
}
複製代碼
對於由於數據改變而分離的子View,會從新附着(attach
)到RecyclerView
上。然而對於移動,添加操做,還會有不一樣的操做,這裏就不依依分析了。
如今被分離的子View已經從新附着到RecyclerView
上,而且數據改變的部分也相應的更新了,剩下的就是繪製工做了。
局部刷新,是以分離和再附着的方式處理那些不受影響的子View,而只處理受影響的子View,或從新綁定後再附着,或直接建立在添加到RecyclerView
。總之,相對於全局刷新,提高了繪製性能。
要使用局部刷新,就必須比較先後的數據差別,而後決定使用哪一種數據刷新方式。比較這個過程每每是複雜的,因此後來Google又推出了一個工具類DiffUtil
,它把這個比較過程抽象出來,經過它能夠計算先後數據差別,而後精準的調用局部刷新的方法。
若是以爲個人文章寫的還不錯,點個贊,關注我。