前言
抽絲剝繭 RecyclerView
系列文章的目的在于幫助Android開發者提高對RecyclerView的認知,本文是整個系列的第三篇。
在前面的系列文章中,我們從源碼的角度分別研究了:
-
RecyclerView
(整體結構) Recycler
LayoutManger
-
ItemDecoration
(略微了解)
縱觀RecyclerView
,似乎還剩下ItemAnimator
和Adapter
,那么本文作為抽絲剝繭RecyclerView
系列的最后一篇,自然要將剩下的部分全部分析完畢(文末有往期文章的鏈接)。
目錄
一、RecyclerView中的魔法師 - Adapter
我將Adapter
稱為RecyclerView
中的魔法師,為什么叫它魔法師呢?因為它將數據變成了具體的視圖,不過這也是我們平時談論頗多的適配器模式。
Adapter
的主要功能是數據轉子視圖和數據管理及通知,所以在了解源碼之前,我們還需了解Adpater
的相關類:
名稱 | 作用 |
---|---|
AdapterDataObservable |
數據發生變化的時候實際處理的類 |
ViewHolder |
存入子視圖 和當前的位置信息,大家應該都很熟悉了~ |
1. 數據轉子視圖
在之前的文章《抽絲剝繭RecyclerView - 化整為零》我們介紹Recycler
的時候,已經了解到在Recycler
如果沒有緩存ViewHolder
,會調用Adapter#onCreateViewHolder
創建一個ViewHolder
,我們平常在該方法的實現中,通常會:
View root = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.xxx,viewGroup,false);
return new ViewHolder(root);
以這樣的方式創建子視圖
,創建完的子視圖
會交給ViewHolder
管理,存儲在ViewHolder中的itemView
,接著Recycler
會調用Adapter#onBindViewHolder
實現將數據展示在控件中,不過這兩個方法都是由控件的使用者實現。
2. 數據管理
每次數據發生變化的時候,我們都需要調用Adapter#notifyxxx
通知RecyclerView
數據集發生了變化。這次我們以刪除為例來分析源碼。
2.1 設置適配器
設置適配器的代碼是RecyclerView#setAdapter
:
public void setAdapter(Adapter adapter) {
// ...
// 重點方法
setAdapterInternal(adapter, false, true);
// ...
}
private void setAdapterInternal(Adapter adapter, Boolean compatibleWithPrevious,
Boolean removeAndRecycleViews) {
if (mAdapter != null) {
// 舊的適配器解除注冊
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
// ...
final Adapter oldAdapter = mAdapter;
// 對新的適配器檢測數據監聽
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
// ...
}
該代碼主要作用有兩點:
- 舊的適配器取消注冊
- 注冊新的適配器中的數據變化的通知對象
數據變化通知對象是這個mObserver
,來看看這個mObserver
是什么:
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
// 數據發生變化的回調接口
// 通知到RecyclerView中
// RecyclerViewDataObserver繼承自AdapterDataObserver
public abstract static class AdapterDataObserver {
public void onChanged() {
// Do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
// fallback to onItemRangeChanged(positionStart, itemCount) if app
// does not override this method.
onItemRangeChanged(positionStart, itemCount);
}
public void onItemRangeInserted(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeRemoved(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
// do nothing
}
}
而這個RecyclerViewDataObserver
則是繼承自AdapterDataObserver
抽象類,具體的實現細節我們后面再討論。
2.2 數據刪除
使用場景是這樣的:
點擊界面中的一個刪除按鈕,刪除數據列表中的第一個數據,然后使用適配器通知數據中已經刪除:
btnDeleteOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
List<String> strings = mAdapter.getValues();
if(strings.size() == 0)
return;
// 移除第一個數據
strings.remove(0);
// 適配器通知刪除
mAdapter.notifyItemRemoved(0);
}
2.3 適配器通知
這里有必要說明一下:Adapter
和RecyclerViewDataObserver
都是RecyclerView
的內部類,所以它們可以直接使用RecyclerView
內部的資源。
當RecyclerView
中的數據刪除的時候,我們調用了Adapter#notifyRemoved
方法:
public final void notifyItemRemoved(int position) {
mObservable.notifyItemRangeRemoved(position, 1);
}
發現刪除的處理交給了上面介紹的mObservable
,我們來看一下RecyclerViewDataObserver#notifyItemRemoved
具體實現:
private class RecyclerViewDataObserver extends AdapterDataObserver {
//... 省略一些方法
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
// 刷新界面或者直接進行動畫
// 刪除這里是調用的刷新界面
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
在RecyclerViewDataObserver
的通知刪除方法中,它又把刪除的處理交給了AdapterHelper
,調用了AdapterHelper#onItemRangeRemoved
:
/**
* @return True if updates should be processed.
*/
Boolean onItemRangeRemoved(int positionStart, int itemCount) {
if (itemCount < 1) {
return false;
}
// mPendingUpdates是List<UpdateOp>
// 這里是將一個刪除的UpdateOp加入mPendingUpdates中
mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
mExistingUpdateTypes |= UpdateOp.REMOVE;
return mPendingUpdates.size() == 1;
}
AdapterHelper
對自己比較有信心,沒有交給別人處理,他在自己的mPendingUpdates
中加入一個刪除標記的UpdateOp
,這個mPendingUpdates
有什么作用呢?我們同樣在使用的時候介紹。回到RecyclerViewDataObserver
中的RecyclerViewDataObserver#notifyItemRemoved
,調用完AdapterHelper#onItemRangeRemoved
之后,它立馬又調用了requestLayout
進行界面刷新。
目錄.png
2.4 界面繪制流程的一些細節
界面繪制一直是我們之前博客的重點討論對象,本章我們就數據通知再看一下關于數據通知的細節。
在RecyclerView#dispatchLayoutStep1
方法中,RecyclerView
會調用RecyclerView#processAdapterUpdatesAndSetAnimationFlags
處理Adapter
中的更新和為動畫設置標記,這里我們只看適配器數據更新相關:
private void processAdapterUpdatesAndSetAnimationFlags() {
//...
// simple animations are a subset of advanced animations (which will cause a
// pre-layout step)
// If layout supports predictive animations, pre-process to decide if we want to run them
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
// ...
}
private Boolean predictiveItemAnimationsEnabled() {
// RecyclerView設置了默認的mItemAnimator,
// 以及LinearLayout的supportsPredictiveItemAnimations()為true
// 該方法返回為true
return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
}
由于RecyclerView#predictiveItemAnimationsEnabled
通常會返回true
,那我們跳到AdapterHelper
,查看AdapterHelper#preProcess
方法:
void preProcess() {
// ...
final int count = mPendingUpdates.size();
for (int i = 0; i < count; i++) {
UpdateOp op = mPendingUpdates.get(i);
switch (op.cmd) {
// ... 添加省略
case UpdateOp.REMOVE:
applyRemove(op);
break;
// 更新、移動標簽省略
}
}
mPendingUpdates.clear();
}
mPendingUpdates
是一個ArrayList<UpdateOp>
,上述方法就是消費我們在之前添加進mPendingUpdates
的刪除UpdateOp
,在處理刪除屬性的UpdateOp
的AdapterHelper#applyRemove
方法中又調用AdapterHelper#postponeAndUpdateViewHolders
:
private void postponeAndUpdateViewHolders(UpdateOp op) {
mPostponedList.add(op);
switch (op.cmd) {
// ... 省略添加、更新、移動
case UpdateOp.REMOVE:
mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,op.itemCount);
break;
default:
throw new IllegalArgumentException("Unknown update op type for " + op);
}
}
真實的處理交給了AdapterHelper
中的mCallback
,而mCallback
的實現同樣也在RecyclerView
,那我們直接查看mCallback
的具體實現:
void initAdapterManager() {
mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
// ...省略其他方法
// 僅僅展示刪除的方法
@Override
public void offsetPositionsForRemovingLaidOutOrNewView(
int positionStart, int itemCount) {
offsetPositionRecordsForRemove(positionStart, itemCount, false);
mItemsAddedOrRemoved = true;
}
//... 省略其他方法
});
}
void offsetPositionRecordsForRemove(int positionStart, int itemCount,
Boolean applyToPreLayout) {
final int positionEnd = positionStart + itemCount;
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderint(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore()) {
if (holder.mPosition >= positionEnd) {
// 更新未刪除的ViewHOlder的的位置信息
holder.offsetPosition(-itemCount, applyToPreLayout);
mState.mStructureChanged = true;
} else if (holder.mPosition >= positionStart) {
// 跟新要刪除逇ViewHolder的位置信息
holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount,
applyToPreLayout);
mState.mStructureChanged = true;
}
}
}
mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
requestLayout();
}
上述代碼的作用主要有兩點:
- 對于要刪除的
ViewHolder
加上刪除的flag
,更新ViewHolder
的位置 - 對于位置會變化的
ViewHolder
則更新位置
在數據刪除以后,Adapter
的作用就是為這些變化的ViewHolder
添加刪除標簽和更新位置信息,后續的處理就交給了LayoutManager
和ItemAnimator
,我們在下面的動畫中分析~
二、界面交互的粘合劑 - ItemAnimator
好的動畫會讓界面的交互很自然,RecyclerView
作為一款強大的UI控件,自然也是支持動畫的,沒錯,RecyclerView
子視圖動畫是由ItemAnimator
實現的。
上文中的Gif不適合講解,于是我換了一張聊天圖,同樣要刪除第一條信息:
上文中,我們討論
Adapter
的結果是它更新了ViewHolder的
一些flag
,那么這些有了flag
的ViewHolder
是如何處理的呢?
在此之前,簡單了解一下動畫相關類ViewInfoStore
:
1. 進行預布局
預布局是什么呢?簡單來說,RecyclerView
進行真實的布局之前,提前進行一次布局,也就是說,LayoutManager#onLayoutChildren
方法會執行兩次,那么為什么會執行兩次呢?我們慢慢分析。
預布局是一個很重要得過程,當有簡單的子視圖動畫發生的時候,它就會被觸發,這一點我們得回顧一下RecyclerView#dispatchLayoutStep1
方法,直接進入其中的RecyclerView#processAdapterUpdatesAndSetAnimationFlags
方法:
private void processAdapterUpdatesAndSetAnimationFlags() {
// simple animations are a subset of advanced animations (which will cause a
// pre-layout step)
// If layout supports predictive animations, pre-process to decide if we want to run them
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
Boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
// mFirstLayoutComplete會在第一次布局完成以后設置為true
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
從上面的代碼中,我們可以看出:
- 第一段注釋中表明,簡單的動畫會觸發預布局
- 當
RecyclerView
第一次布局完成以后才有資格觸發動畫,mFirstLayoutComplete
是在第一次布局完成以后設置為true
-
mState.mRunSimpleAnimations
為true
是mState.mRunPredictiveAnimations
為true
的充要條件,mState.mRunPredictiveAnimations
這個屬性很重要,由它決定是否進行預布局
重新回到RecyclerView#dispatchLayoutStep1
方法:
private void dispatchLayoutStep1() {
// ...
mViewInfoStore.clear();
processAdapterUpdatesAndSetAnimationFlags();
// 重置一些狀態
// ... 省略
mItemsAddedOrRemoved = mItemsChanged = false;
// 是否預布局取決于mState.mRunPredictiveAnimations
mState.mInPreLayout = mState.mRunPredictiveAnimations;
if (mState.mRunSimpleAnimations) {
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderint(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
// 記錄當前的位置信息 Left、Right、Top、Bottom等
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
// ... 省略
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
// 大致就是layoutManager會layout每一個子視圖,包括后面加入的子視圖和刪除的子視圖,這樣以后,layoutManager就很清楚
// 要執行哪些動畫了
saveOldPositions();
final Boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderint(child);
// ...
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
// 對于新出來的ViewHolder添加標簽
// ... 省略一些方法
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
// ...
}
除了上面直接進入的方法,還有兩個if
語句。
第一個if語句:mState.mRunSimpleAnimations為true
這個內容很簡單,對預布局前存在的ViewHolder
的的位置信息進行記錄。
第二個if語句:mState.mRunPredictiveAnimations為true
第二個if語句的內容就復雜多了,首先會進行預布局過程,該過程第一次調用了LayoutManager#onLayoutChildren
,關于布局的具體過程,這里我就不講解了,想要了解的同學可以翻閱我之前的文章:《抽絲剝繭RecyclerView - LayoutManager》。
需要指出的是,在添加子視圖中,調用了LayoutManager#addViewInt
方法:
private void addViewint(View child, int index, Boolean disappearing) {
// ...
if (disappearing || holder.isRemoved()) {
// these views will be hidden at the end of the layout pass.
mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
} else {
// This may look like unnecessary but may happen if layout manager supports
// predictive layouts and adapter removed then re-added the same item.
// In this case, added version will be visible in the post layout (because add is
// deferred) but RV will still bind it to the same View.
// So if a View re-appears in post layout pass, remove it from disappearing list.
mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
}
// ...
}
該方法的目的是如果是被刪除的ViewHolder
,它會為ViewInfoStore
中ViewHolder
對應的記錄InfoRecord
添加已刪除的標記,在真實的布局(非預布局)中,被刪除的ViewHolder
是不會被使用的,所以說,只有預布局才會記錄刪除動畫。
可以看到,第一次布局完了以后,需要刪除的ViewHolder
和自動填充的ViewHolder
都被加入了RecyclerView
,不過,RecyclerView#DispatchLayoutStep1
還沒結束,它會調用ViewInfoStore
會給新加入的ViewHolder
添加對應的InfoRecord
。
完成這個以后,RecyclerView
對于要處理哪些動畫就了如指掌了,這個也是預布局的意義。
2. 真實布局
同樣不講具體的代碼,第二次布局完成以后,界面變成了:
看到上面的圖,你可能會有這樣的疑問,為什么要刪除的子視圖沒了?說好不講代碼的,打臉,只能甩出一段緩存
Recycler
的源碼了??:
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, Boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (... && (mState.mInPreLayout || !holder.isRemoved())) {
// 在第一級緩存mAttachedScrap中,如果是刪除的ViewHolder
// 預布局是可以使用的,真實布局不可以使用
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
//...
return null;
}
雖然解決了當前的疑問,你可能還會有另外一個疑問,沒有了被刪除的子視圖,刪除動畫還怎么執行呢?我們還是先看看接下來的過程吧。
3. 執行動畫
之前我們記錄了那么多ViewHolder
中子視圖的信息,現在到了使用的時候了:
private void dispatchLayoutStep3() {
// ...
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now, and process change animations.
// 找到當前的ViewHolder,執行需要執行的動畫
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderint(mChildHelper.getChildAt(i));
long key = getChangedHolderKey(holder);
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
// ...
if (oldDisappearing && oldChangeViewHolder == holder) {
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
// ...
mViewInfoStore.addToPostLayout(holder, animationInfo);
ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
// ...
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// Step 4: Process view info lists and trigger animations
// 執行動畫
mViewInfoStore.process(mViewInfoProcessCallback);
}
// 重置一些跟動畫有關的類
// ...
mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;
// ...
mViewInfoStore.clear();
}
這個函數的上半部分主要的目的是為了給ViewInfoStore
里的ViewHolder
相關的InfoRecord
添加Post
標簽,下半部分mViewInfoStore.process(mViewInfoProcessCallback)
則是我們的核心功能 - 動畫執行,我們重點看一下這個方法:
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
final InfoRecord record = mLayoutHolderMap.removeAt(index);
// 根據不同的Flag執行不同的動畫
if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
callback.unused(viewHolder);
} else if ((record.flags & FLAG_DISAPPEARED) != 0) {
// Set as "disappeared" by the LayoutManager (addDisappearingView)
if (record.preInfo == null) {
callback.unused(viewHolder);
} else {
callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
}
} else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
// Appeared in the layout but not in the adapter (e.g. entered the viewport)
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
// Persistent in both passes. Animate persistence
callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE) != 0) {
// Was in pre-layout, never been added to post layout
callback.processDisappeared(viewHolder, record.preInfo, null);
} else if ((record.flags & FLAG_POST) != 0) {
// Was not in pre-layout, been added to post layout
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_APPEAR) != 0) {
// Scrap view. RecyclerView will handle removing/recycling this.
} else if (DEBUG) {
throw new IllegalStateException("record without any reasonable flag combination:/");
}
InfoRecord.recycle(record);
}
}
// 回調接口
interface ProcessCallback {
void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
@Nullable ItemHolderInfo postInfo);
void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
ItemHolderInfo postInfo);
void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
@NonNull ItemHolderInfo postInfo);
void unused(ViewHolder holder);
}
在ViewInfoStore#process
這個關鍵方法中,遍歷mLayoutHolderMap
獲取ViewHolder
綁定的InfoRecord
,根據不同flag
的InfoRecord
,回調不同的方法,進而處理不同的動畫,回調接口的實現在RecyclerView
中:
/**
* The callback to convert view info diffs into animations.
*/
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
@Override
public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
@Nullable ItemHolderInfo postInfo) {
// 先移除緩存中的ViewHolder
mRecycler.unscrapView(viewHolder);
animateDisappearance(viewHolder, info, postInfo);
}
@Override
public void processAppeared(ViewHolder viewHolder,
ItemHolderInfo preInfo, ItemHolderInfo info) {
// 出現的動畫
animateAppearance(viewHolder, preInfo, info);
}
@Override
public void processPersistent(ViewHolder viewHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
viewHolder.setIsRecyclable(false);
if (mDataSetHasChangedAfterLayout) {
// since it was rebound, use change instead as we'll be mapping them from
// stable ids. If stable ids were false, we would not be running any
// animations
if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
postInfo)) {
postAnimationRunner();
}
} else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
postAnimationRunner();
}
}
@Override
public void unused(ViewHolder viewHolder) {
mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
}
};
如果你注意到刪除方法,你的疑問就更大了,刪除的子視圖都沒了,還執行毛線刪除動畫?那我就得告訴你了:雖然當前RecyclerView
沒有需要刪除的子視圖,但是當前的ViewInfoStore
有ViewHolder
啊,所以在執行刪除動畫前會將ViewHolder
中的子視圖重新添加到RecyclerView
里面,這里看一下上面的processDisappeared
方法調用的RecyclerView#animateDisappearance
方法,來看看是不是這樣的:
void animateDisappearance(@NonNull ViewHolder holder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
// 將子視圖重新加入到界面
addAnimatingView(holder);
holder.setIsRecyclable(false);
// mItemAnimator執行刪除動畫
if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
/**
* 將刪除的子視圖重新添加進界面
*/
private void addAnimatingView(ViewHolder viewHolder) {
final View view = viewHolder.itemView;
final Boolean alreadyParented = view.getParent() == this;
mRecycler.unscrapView(getChildViewHolder(view));
if (viewHolder.isTmpDetached()) {
// 重新attach回界面
mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
} else if (!alreadyParented) {
// 添加視圖
mChildHelper.addView(view, true);
} else {
mChildHelper.hide(view);
}
}
一圖了解當前界面ViewHolder的狀態和需要執行的動畫:
如上面代碼塊中所看到的,刪除子視圖的動畫實際的執行者是
mItemAnimator
,其他動畫也是如此。
4. DefaultItemAnimator機制
ItemAnimator
是一個抽象類,所以一些方法需要具體的類實現,在沒有指定具體的ItemAnimator
情況下,系統使用了默認的DefaultItemAnimator
。一圖簡單了解DefaultItemAnimator
機制:
除了
DefaultItemAnimator
,你還可以自定義一個ItemAnimator
,主要實現增、刪、更新和移動等一些方法,本文就不再深入了,感興趣的同學可以自行研究。
在DefaultItemAnimator
的刪除動畫中,會對被刪除的子視圖執行透明度1-0的動畫,動畫結束后,會刪除子視圖和回收ViewHolder
,位移動畫沒有放在透明度動畫結束后調用,而是使用時間為透明度動畫執行時間的延遲,所以看上去就像子視圖被刪除后下面的子視圖才開始網上位移的。
動畫執行完畢以后,圖片就變成了:
以上就是RecyclerView
刪除部分的Adapter
和ItemAnimator
的調用原理,其他方法同學們可以自行分析~
三、總結
還是那句話,沒有一遍調試解決不了源碼閱讀,如果有,那就是兩遍??~,抽絲剝繭RecyclerView源碼分析到此就結束了。
如果你想繼續了解RecyclcerView
:
第一篇:《抽絲剝繭RecyclerView - 化整為零》
第二篇:《抽絲剝繭RecyclerView - LayoutManager》
特別分享篇: