介紹
在上一篇中,我們分析了RecyclerView的繪制與複用。接下來我們繼續分析RecyclerView的動畫實作原理。上圖展示的是一個Recyclerview的中,某個Item的删除,從動畫執行preLayout階段-> postLayout階段 ->動畫執行-> 動畫結束後,item的回收 的整個流程。
1. notifyItemXXX的作用
在RecyclerView的,我們通過Adapter實作資料與View的綁定。當資料更改時,通過notifyItemXXX實作資料的更新。接下我們就首先分析一下notifyItemXXX究竟做了一件什麼事情(以notifyItemRemoved為例)。
/**
* RecyclerView$Adapter類
*/
public abstract static class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
public final void notifyItemRemoved(int position) {
//觀察者模式,将item移除事件通知類 “觀察者”
//其中RecyclerViewDataObserver就是“觀察者之一”
mObservable.notifyItemRangeRemoved(position, 1);
}
}
/**
* RecyclerView内置的觀察者
*/
private class RecyclerViewDataObserver extends AdapterDataObserver {
//
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
//通過mAdapterHelper,記錄“删除操作”
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
//調用成員方法
triggerUpdateProcessor();
}
}
//該方法中,請求requestLayout進行重新繪制
void triggerUpdateProcessor() {
...
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
/**
* AdapterHelper.java
* 看一下AdapterHelper是如何記錄 “删除操作”
*/
boolean onItemRangeRemoved(int positionStart, int itemCount) {
if (itemCount < 1) {
return false;
}
//将“删除操作”封裝成對象,并保持到mPendingUpdates中
//在重新繪制的,PreLayout階段進行使用。實作狀态的同步。
mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
mExistingUpdateTypes |= UpdateOp.REMOVE;
return mPendingUpdates.size() == 1;
}
2. RecyclerView的兩個布局階段
為什麼RecylerView的動畫要分為兩個階段
衆所衆知,ViewGroup可以通過LayoutTransition實作其内部childView的動畫變化。在普通的ViewGroup,其内部的View的動畫就是顯示和隐藏。但是ReyclerView需要的動畫有些不同,因為它有滾動的效果。比如:如下圖所示,我們要删除Item C,Item G進入螢幕,會給人一種誤解–在RecyclerView的尾部新增了Item G。
我們期望的效果是:
要想實作上圖中的預期效果,我們需要記錄螢幕中被影響變化的Item的 前後的位置變化和資料變化。而 PreLayout 和 PostLayout就是用來分别做這兩件事情的。
PreLayout:
在該階段,RecyclerView要求LayoutManager使用附加資訊布局之前的狀态。比如:它會請求重新繪制items(此時C已經被删除了),LayoutManager運作它往常的布局步驟,但知道’C’将被删除,它布局items來填充’C’留下的空間。這個設計最酷的地方是,RecyclerView仍然表現得好像“C”仍然在Adapter中一樣。
For example, when LayoutManager asks for the View for position 2, RecyclerView returns ‘C’ (getViewForPosition(2) == View(‘C’)) and if LayoutManager asks for position 4, RecyclerView returns the View for ‘E’ (although ‘D’ is the 4th item in the Adapter).
傳回ItemView的LayoutParams有一個isItemRemove方法,LayoutManager可以使用該方法檢查這是否是一個要删除的item。
PostLayout
‘C’ is not in the Adapter anymore. getViewForPosition(2) will return ‘D’ and getViewForPosition(4) will return ‘F’.
請記住,“C”的背景項已經從擴充卡中删除了,但是由于RecyclerView有它的視圖表示,它可以表現得好像“C”還在那裡一樣。換句話說,RecyclerView為LayoutManager記賬。
2.1 PreLayout分析
private void dispatchLayoutStep1() {
...
if (mState.mRunSimpleAnimations) {
// Step 0: 找出沒有被删除的Item
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;
}
//擷取目前item的位置資訊
final ItemAnimator.ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
//将item的位置資訊放入到mViewInfoStore中進行緩存
mViewInfoStore.addToPreLayout(holder, animationInfo);
}
}
//processAdapterUpdatesAndSetAnimationFlags方法中設定的标記
if (mState.mRunPredictiveAnimations) {
// 儲存原有位置的資訊
// Save old positions so that LayoutManager can run its mapping logic、
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
//進行預繪制,将ViewHolder中的狀态資訊,同步到ViewInfoStroe中
//便于在PostLayout中,執行動畫
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
...
clearOldPositions();
} else {
//擦除位置資訊
clearOldPositions();
}
...
}
2.2 PostLayout分析
private void dispatchLayoutStep3() {
//進入動畫繪制階段
mState.assertLayoutStep(State.STEP_ANIMATIONS);
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
...
//回去布局後的,holder的目前位置資訊
final ItemAnimator.ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
...
//将 postLayout階段的 animationInfo 緩存到mViewInfoStore中
mViewInfoStore.addToPostLayout(holder, animationInfo);
}else{
...
//将 postLayout階段的 animationInfo 緩存到mViewInfoStore中
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
} else {
//将 postLayout階段的 animationInfo 緩存到mViewInfoStore中
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
//開始動畫的執行
mViewInfoStore.process(mViewInfoProcessCallback);
}
...
}
3. 動畫的執行
接下來,我們接着上一節,繼續分析動畫。
// ViewInfoStore.java
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final InfoRecord record = mLayoutHolderMap.removeAt(index);
if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
callback.unused(viewHolder);
} else if ((record.flags & FLAG_DISAPPEARED) != 0) {
...
//根據record.flags的不同狀态,執行不同的動畫
//record.flags狀态的設定,源于 preLayout階段的,LayoutManager.onLayoutChildren()方法
callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
} else{
...
}
//回收record info
InfoRecord.recycle(record);
}
}
接下來,看一下callback是怎麼具體處理動畫的。
/**
* 該成員變量 隸屬于 RecyclerView
*/
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
@Override
public void processDisappeared(ViewHolder viewHolder, @NonNull ItemAnimator.ItemHolderInfo info,
@Nullable ItemAnimator.ItemHolderInfo postInfo) {
//将viewHolder從一級緩存中去除
mRecycler.unscrapView(viewHolder);
//調用執行動畫的邏輯
animateDisappearance(viewHolder, info, postInfo);
}
};
void animateDisappearance(@NonNull ViewHolder holder,
@NonNull ItemAnimator.ItemHolderInfo preLayoutInfo, @Nullable ItemAnimator.ItemHolderInfo postLayoutInfo) {
//確定holder對應的View,已經被add到RecyclerView之上
//這是執行動畫的基礎之一。如果是removed的holder,則需要進行hide操作
addAnimatingView(holder);
holder.setIsRecyclable(false);
//到了這裡,mItemAnimator,是不是就不陌生了
//這裡是需要執行的緩存動畫
if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
//觸發動畫的執行
postAnimationRunner();
}
}
//注意,如果是“删除操作”,在動畫執行完成的回掉裡面,需要調用方法
//dispatchRemoveFinished(holder);進行holder的dettach和recycle
/**
* RecyclerView的成員變量
*/
private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {
ItemAnimatorRestoreListener() {
}
@Override
public void onAnimationFinished(ViewHolder item) {
item.setIsRecyclable(true);
//如果item需要回收
if (!item.shouldBeKeptAsChild()) {
//進行回收
if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
//進行detach并且通知監聽器
removeDetachedView(item.itemView, false);
}
}
}
}
上一篇RecyclerView源碼分析二之繪制與複用
參考連結:
- RecyclerView動畫原理分析一
- RecyclerView動畫原理分析二