介紹
在上一篇中,我們分析了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);
}
}
}
}
參考鏈接: