網站首頁 編程語言 正文
前言
在這個系列博客的第二篇的最后部分提到了預布局,在預布局階段,計算剩余空間時會把將要移除的 ViewHolder 忽略,從而計算出遞補的 ViewHolder,在 ViewHolder 移除、新增、更新時都可以觸發默認動畫(也可以自定義動畫),那么動畫部分到底是怎么實現的呢?本篇博客將針對 ItemAnimator 的運作流程部分源碼進行分析。
源碼分析
前置基礎
一般我們給 RecyclerView 設置動畫會調用 setItemAnimator 方法,直接看一下源碼:
RecyclerView.java
// 默認動畫 DefaultItemAnimator ItemAnimator mItemAnimator = new DefaultItemAnimator(); public void setItemAnimator(@Nullable ItemAnimator animator) { if (mItemAnimator != null) { // 先結束動畫,取消監聽 mItemAnimator.endAnimations(); mItemAnimator.setListener(null); } mItemAnimator = animator; // 賦值 if (mItemAnimator != null) { // 重新設置監聽 mItemAnimator.setListener(mItemAnimatorListener); } }
可以看出 RecyclerView 默認提供了 DefaultItemAnimator,先不著急分析它,首先我們要分析出動畫的執行流程以及動畫的信息是怎么處理的。先了解以下這么幾個類作為基礎。
ItemHolderInfo
RecylerView.java
public static class ItemHolderInfo { public int left; public int top; public int right; public int bottom; @AdapterChanges public int changeFlags; public ItemHolderInfo() { } public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder) { return setFrom(holder, 0); } @NonNull public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder, @AdapterChanges int flags) { final View view = holder.itemView; this.left = view.getLeft(); this.top = view.getTop(); this.right = view.getRight(); this.bottom = view.getBottom(); return this; } }
ItemHolderInfo 作為 RecyclerView 的內部類,代碼非常簡單,向外暴露 setFrom 方法,用于存儲 ViewHolder 的位置信息;
InfoRecord
InfoRecord 是 ViewInfoStore 的內部類(下一小節分析),代碼也非常簡單:
ViewInfoStore.java
static class InfoRecord { // 一些 Flag 定義 static final int FLAG_DISAPPEARED = 1; // 消失 static final int FLAG_APPEAR = 1 << 1; // 出現 static final int FLAG_PRE = 1 << 2; // 預布局 static final int FLAG_POST = 1 << 3; // 真正布局 static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED; static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST; static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST; // 這個 flags 要記住!! // 后面多次對其進行賦值,且執行動畫時也根據 flags 來判斷動畫類型; int flags; // ViewHolder 坐標信息 RecyclerView.ItemAnimator.ItemHolderInfo preInfo; // 預布局階段的 RecyclerView.ItemAnimator.ItemHolderInfo postInfo; // 真正布局階段的 // 池化 提高效率 static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20); // ... // 其內部的一些方法都是復用池相關 特別簡單 就不貼了 }
不難看出,InfoRecord 功能和他的名字一樣信息記錄,主要記錄了預布局、真正布局兩個階段的 ViewHodler 的位置信息(ItemHolderInfo)。
ViewInfoStore
class ViewInfoStore { // 將 ViewHodler 和 InfoRecord 以鍵值對形式存儲 final SimpleArrayMap<RecyclerView.ViewHolder, InfoRecord> mLayoutHolderMap = new SimpleArrayMap<>(); // 根據坐標存儲 ViewHodler 看名字也看得出是 舊的,舊是指: // 1.viewHolder 被隱藏 但 未移除 // 2.隱藏item被更改 // 3.預布局跳過的 item final LongSparseArray<RecyclerView.ViewHolder> mOldChangedHolders = new LongSparseArray<>(); // mLayoutHolderMap 中添加一項 如果有就改變 InfoRecord 的值 // 下面很多方法都是類似功能 下面的就不貼 if 里面那段了 void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) { InfoRecord record = mLayoutHolderMap.get(holder); if (record == null) { // 沒有就構建一個 加入 map record = InfoRecord.obtain(); mLayoutHolderMap.put(holder, record); } record.preInfo = info; record.flags |= FLAG_PRE; // 跟方法名對應的 flag } // 調用 popFromLayoutStep 傳遞 FLAG_PRE RecyclerView.ItemAnimator.ItemHolderInfo popFromPreLayout(RecyclerView.ViewHolder vh) { return popFromLayoutStep(vh, FLAG_PRE); } // 調用 popFromLayoutStep 傳遞 FLAG_POST RecyclerView.ItemAnimator.ItemHolderInfo popFromPostLayout(RecyclerView.ViewHolder vh) { return popFromLayoutStep(vh, FLAG_POST); } // 上面兩個方法都調用的這里 flag 傳遞不同 private RecyclerView.ItemAnimator.ItemHolderInfo popFromLayoutStep(RecyclerView.ViewHolder vh, int flag) { int index = mLayoutHolderMap.indexOfKey(vh); if (index < 0) { return null; } // 從map中獲取 final InfoRecord record = mLayoutHolderMap.valueAt(index); if (record != null && (record.flags & flag) != 0) { record.flags &= ~flag; final RecyclerView.ItemAnimator.ItemHolderInfo info; if (flag == FLAG_PRE) { // 根據 flag 獲取對應的 ItemHolderInfo info = record.preInfo; } else if (flag == FLAG_POST) { info = record.postInfo; } else { throw new IllegalArgumentException("Must provide flag PRE or POST"); } // 如果沒有包含兩個階段的flag 直接回收 if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) { mLayoutHolderMap.removeAt(index); InfoRecord.recycle(record); } return info; } return null; } // 向 mOldChangedHolders 添加一個 holder void addToOldChangeHolders(long key, RecyclerView.ViewHolder holder) { mOldChangedHolders.put(key, holder); } // 和 addToPreLayout 方法類似 flags 不同 void addToAppearedInPreLayoutHolders(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) { InfoRecord record = mLayoutHolderMap.get(holder); // ... record.flags |= FLAG_APPEAR; record.preInfo = info; } // 和 addToPreLayout 方法類似 flags 不 // 注意這里的方法名 是添加的 post-layout 真正布局階段的信息 void addToPostLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) { InfoRecord record = mLayoutHolderMap.get(holder); // ... record.postInfo = info; // 這里賦值的是 postInfo record.flags |= FLAG_POST; } // 這里直接拿到 InfoRecord 修改了 flag void addToDisappearedInLayout(RecyclerView.ViewHolder holder) { InfoRecord record = mLayoutHolderMap.get(holder); // ... record.flags |= FLAG_DISAPPEARED; } // 這里直接拿到 InfoRecord 修改了 flag void removeFromDisappearedInLayout(RecyclerView.ViewHolder holder) { InfoRecord record = mLayoutHolderMap.get(holder); // ... record.flags &= ~FLAG_DISAPPEARED; } // 移除 兩個容器都移除 void removeViewHolder(RecyclerView.ViewHolder holder) { //... mOldChangedHolders.removeAt(i); //... final InfoRecord info = mLayoutHolderMap.remove(holder); } // 這里其實是 動畫開始的入口 void process(ProcessCallback callback) { // 倒著遍歷 mLayoutHolderMap for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) { final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index); final InfoRecord record = mLayoutHolderMap.removeAt(index); // 取出 InfoRecord 根據 flag 和 兩個階段位置信息 進行判斷 觸發對應的 callback 回調方法 if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) { callback.unused(viewHolder); } // 一大堆判斷就先省略了,后面會提到 ... // ... // 最后回收 InfoRecord.recycle(record); } } // ... }
ViewInfoStore,字面翻譯為 View信息商店,類名就體現出了他的功能,主要提供了對 ViewHolder 的 InfoRecord 存儲以及修改,并且提供了動畫觸發的入口。
ProcessCallback
還有最后一個類需要了解,也就是上面 ViewInfoStore 最后一個方法 process 中用到的 callback,直接看源碼:
ViewInfoStore.java
//前三個需要做動畫的方法傳入了 viewHolder 以及其預布局、真正布局兩個階段的位置信息 interface ProcessCallback { // 進行消失 void processDisappeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo); // 進行出現 void processAppeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo); // 持續 也就是 不變 或者 數據相同大小改變的情況 void processPersistent(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo); // 未使用 void unused(RecyclerView.ViewHolder holder); }
ProcessCallback 在 RecyclerView 有默認實現,這個待會再詳細分析,看 callback 的方法名也能略知一二,分別對應 ViewHolder 做動畫的幾種情況;
那么從前三個方法的參數中也能推斷出,ViewHolder 做動畫時,動畫的數據也是從 preInfo 和 postInfo 兩個參數中做計算得出。
動畫處理
前置基礎有點多??????,不過通過對上面幾個類有一些了解,下面在分析動畫觸發、信息處理時就不用反復解釋一些變量的意義了。
dispatchLayoutStep3
在之前分析布局階段的博客中提到 dispatchLayoutStep1、2、3 三個核心方法,分別對應三種狀態 STEP_START、STEP_LAYOUT、STEP_ANIMATIONS;
很顯然,STEP_ANIMATIONS 是執行動畫的階段,再來看一下 dispatchLayoutStep3 方法中對 item 動畫進行了哪些操作:
RecyclerView.java
private void dispatchLayoutStep3() { // ... if (mState.mRunSimpleAnimations) { // 需要做動畫 // 倒著循環 因為可能會發生移除 for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) { // 獲取到 holder ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); if (holder.shouldIgnore()) { // 如果被標記忽略則跳過 continue; } // 獲取 holder 的 key 一般情況獲取的就是 position long key = getChangedHolderKey(holder); // 前置基礎中 提到的 ItemHolderInfo // recordPostLayoutInformation 內部構建了一個 ItemHolderInfo 并且調用了 setFrom 設置了 位置信息 final ItemHolderInfo animationInfo = mItemAnimator.recordPostLayoutInformation(mState, holder); // 從 ViewInfoStore 的 mOldChangedHolders 中獲取 vh ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) { // 是否正在執行消失動畫 final boolean oldDisappearing = mViewInfoStore.isDisappearing(oldChangeViewHolder); final boolean newDisappearing = mViewInfoStore.isDisappearing(holder); // 這個 if 判斷 將要發生更新動畫的 vh 已經在執行消失動畫 if (oldDisappearing && oldChangeViewHolder == holder) { // 用消失動畫代替 mViewInfoStore.addToPostLayout(holder, animationInfo); } else { // 獲取 預布局階段的 ItemHolderInfo final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(oldChangeViewHolder); // 設置 真正布局階段的 ItemHolderInfo mViewInfoStore.addToPostLayout(holder, animationInfo); // 然后在取出來 ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder); if (preInfo == null) { handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder); } else { // 用上面取出來的 preInfo postInfo 做更新動畫 animateChange(oldChangeViewHolder, holder, preInfo, postInfo,oldDisappearing, newDisappearing); } } } else { // 沒有獲取到 直接設置 hodler 真正布局階段的 位置信息 并且設置 flag mViewInfoStore.addToPostLayout(holder, animationInfo); } } // 開始執行動畫 mViewInfoStore.process(mViewInfoProcessCallback); } // ... }
代碼中的注釋寫的比較詳細,主要注意一下 mViewInfoStore.addToPostLayout
會給 ViewHolder 生成 InfoRecord 對象,并且設置 postInfo,并且給 flags 添加 FLAG_POST,然后以 <ViewHolder, InfoRecord> 鍵值對形式添加到 ViewInfoStore 的 mLayoutHolderMap 中;
dispatchLayoutStep1
其實上一小節 dispatchLayoutStep3 方法中也包含對動畫信息的處理,也就是針對真正布局后的位置信息設置的相關代碼。那么刪除、新增的動畫在哪里實現呢?首先,回顧一下之前分析的布局流程,真正的布局發生在 dispatchLayoutStep2 中,預布局發生在 dispatchLayoutStep1 中,結合之前對預布局的簡單解釋,不難理解出預布局時肯定也對動畫信息進行了處理,那么直接看一下 dispatchLayoutStep1 的相關源碼,這部分需要分成兩段來分析,先看第一段:
RecyclerView.java
private void dispatchLayoutStep1() { // ... if (mState.mRunSimpleAnimations) { // 遍歷 child int count = mChildHelper.getChildCount(); for (int i = 0; i < count; ++i) { // 獲取 vh final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); // 忽略、無效的 跳過 if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) { continue; } // 構造出 ItemHolderInfo final ItemHolderInfo animationInfo = mItemAnimator .recordPreLayoutInformation(mState, holder, ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), holder.getUnmodifiedPayloads()); // 注意這里 時 addToPreLayout. 表示預布局階段 // 此時設置的是 InfoRecord 的 preInfo,flag 是 FLAG_PRE mViewInfoStore.addToPreLayout(holder, animationInfo); // 如果 holder 發生改變 添加到 ViewInfoStore 的 mOldChangedHolders 中 if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved() && !holder.shouldIgnore() && !holder.isInvalid()) { long key = getChangedHolderKey(holder); // 獲取 key 一般是 position mViewInfoStore.addToOldChangeHolders(key, holder); } } } // ... }
這一段也不復雜,記錄當前 holder 預布局階段的位置信息(InfoRecord 的 preInfo)到 ViewInfoStore 的 mLayoutHolderMap 中,且添加了 FLAG_PRE 到 flags 中;
并且如果 holder 發生改變就添加到 ViewInfoStore 的 mOldChangedHolders 中;
再看下面的代碼:
RecyclerView.java
private void dispatchLayoutStep1() { // ... if (mState.mRunPredictiveAnimations) { // ... // 這次是預布局 計算可用空間時忽略了要刪除的項目 所以如果發生刪除 會有新的 item 添加進去 mLayout.onLayoutChildren(mRecycler, mState); // ... // 遍歷 child for (int i = 0; i < mChildHelper.getChildCount(); ++i) { final View child = mChildHelper.getChildAt(i); final ViewHolder viewHolder = getChildViewHolderInt(child); if (viewHolder.shouldIgnore()) { continue; } // 這個判斷也就是沒有經歷過上一部分代碼的 vh (onLayoutChildren 中新加入的 item) // InfoRecord 為 null 或者 flags 不包含 FLAG_PRE if (!mViewInfoStore.isInPreLayout(viewHolder)) { int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder); // 判斷是否是隱藏的 boolean wasHidden = viewHolder .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); if (!wasHidden) { // 沒有隱藏 則標記在預布局階段出現 flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; } // 構造出 ItemHolderInfo final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation( mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); if (wasHidden) { // 隱藏的 如果發生更新 并且沒有被移除 就添加到 mOldChangedHolders // 設置 preInfo 設置 flag為 FLAG_PRE recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo); } else { // 沒有隱藏的 設置 flag FLAG_APPEAR, 并且設置 preInfo mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); } } } clearOldPositions(); } else { clearOldPositions(); } // ... }
這里結合之前解釋預布局時的圖來理解下:
第一部分執行時,item1、2、3 都會執行 addToPreLayout,addToPreLayout 會生成 InfoRecord 并且設置其 preInfo 存儲 vh 的位置信息,然后以 <ViewHolder, InfoRecord> 鍵值對形式添加到 ViewInfoStore 的 mLayoutHolderMap 中;
然后第二部分執行了 onLayoutChildren 進行了預布局,以 LinearLayoutManager 為例,在計算可用空間時會忽略要刪除的 item3,從而 item4 被添加到 RecyclerView 中,再次對 child 進行遍歷時進行 mViewInfoStore.isInPreLayout(viewHolder)
判斷時顯然 item4 對應的 ViewHolder 在 mLayoutHolderMap 中獲取為 null,那么就能知道 item4 屬于新增出來的,就在最后調用 mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
生成 InfoRecord 設置位置信息,并且添加 flag 為 FLAG_APPEAR 添加到 mLayoutHolderMap 中。
總結
這部分源碼是倒著來分析的(先看 dispatchLayoutStep3 在看 1 ),可能有點不好理解,先從這三個布局核心方法的角度來稍稍總結一下(均假設需要執行動畫):
dispatchLayoutStep1
- 首先將當前屏幕中的 items 信息保存;(生成 ItemHolderInfo 賦值給 InfoRecord 的 preInfo 并且對其 flags 添加 FLAG_PRE ,再將 InfoRecord 添加到 ViewInfoStore 的 mLayoutHolderMap 中)
- 進行預布局;(調用 LayoutManager 的 onLayoutChildren)
- 預布局完成后和第 1 步中保存的信息對比,將新出現的 item 信息保存;(和第 1 步中不同的是 flags 設置的是 FLAG_APPEAR)
dispatchLayoutStep2
- 將預布局 boolean 值改為 flase;
- 進行真正布局;(調用 LayoutManager 的 onLayoutChildren)
dispatchLayoutStep3
- 將真正布局后屏幕上的 items 信息保存;(與 dispatchLayoutStep1 不同的是賦值給 InfoRecord 的 postInfo 并且 flags 添加 FLAG_POST)
- 執行動畫,調用 ViewInfoStore.process;
- 布局完成回調,onLayoutCompleted;
動畫執行
經過上面兩個 dispatchLayoutStep1 和 3 方法的執行,ViewInfoStore 中已經有預布局時 item 的信息、真正布局后的 item 信息、以及對應的 flags。最終調用了 ViewInfoStore 的 process 執行動畫:
這里面的代碼不難,就是根據 flags 進行判斷執行對應的動畫,調用的是 ProcessCallback 中的方法進行執行,那么看一下 ProcessCallback 的具體實現:
RecyclerView.java
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback = new ViewInfoStore.ProcessCallback() { // ... // 這里就以執行新增動畫為例 其他的也都差不多 @Override public void processAppeared(ViewHolder viewHolder, ItemHolderInfo preInfo, ItemHolderInfo info) { // 調用 animateAppearance animateAppearance(viewHolder, preInfo, info); } // ... }; void animateAppearance(@NonNull ViewHolder itemHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { // 先標記 vh 不能被回收 itemHolder.setIsRecyclable(false); // mItemAnimator 上面也提過了 又默認實現 待會再分析 if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { // 這里看方法名也知道是 post 一個 runnable postAnimationRunner(); } } void postAnimationRunner() { if (!mPostedAnimatorRunner && mIsAttached) { // 核心就是 post 的 mItemAnimatorRunner ViewCompat.postOnAnimation(this, mItemAnimatorRunner); mPostedAnimatorRunner = true; } private Runnable mItemAnimatorRunner = new Runnable() { @Override public void run() { if (mItemAnimator != null) { // 有調用到了 mItemAnimator 中 mItemAnimator.runPendingAnimations(); } mPostedAnimatorRunner = false; } };
整個調用下來核心就在于 ItemAnimator 的兩個方法調用(animateAppearance、runPendingAnimations),那么下面我們就來進入 ItemAnimator 的分析;
ItemAnimator
在最開始的前置基礎小節提到 mItemAnimator 實際上是 DefaultItemAnimator;而 DefaultItemAnimator 繼承自 SimpleItemAnimator,SimpleItemAnimator 又繼承自 ItemAnimator。ItemAnimator 是 RecyclerView 的內部類,其內部大部分是抽象方法需要子類實現,就簡單說說其主要功能不貼代碼了:
- ItemHolderInfo 是 ItemAnimator 的內部類,用于保存位置信息;
- ItemAnimatorListener 是其內部動畫完成時的回調接口;
- 提供設置動畫時間、動畫執行、動畫開始結束回調、動畫狀態的方法,大部分是需要子類實現的;
而上述提供的 animateAppearance 和 runPendingAnimations 都是抽象方法,這里并沒有實現;
SimpleItemAnimator
SimpleItemAnimator 繼承自 ItemAnimator,乍一看方法很多,大部分都是空實現或抽象方法:
這一堆 dispatchXXX 方法和 onXXX 方法是一一對應的,dispatchXXX 中調用 onXXX,而 onXXX 都是空方法交給子類去實現,這部分代碼很簡單就不貼了;
SimpleItemAnimator 實現了 animateAppearance:
public boolean animateAppearance(RecyclerView.ViewHolder viewHolder,ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) { if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left || preLayoutInfo.top != postLayoutInfo.top)) { return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top, postLayoutInfo.left, postLayoutInfo.top); } else { return animateAdd(viewHolder); } }
邏輯很簡單,如果 preLayoutInfo 不為空,并且 preLayoutInfo 和 postLayoutInfo 的 top、left 不同則調用 animateMove 否則調用 animateAdd;看名字也大致能理解是處理移除動畫和添加動畫;
對于 runPendingAnimations SimpleItemAnimator 還是沒有實現;
DefaultItemAnimator
DefaultItemAnimator 繼承自 SimpleItemAnimator,上述兩個父類中都沒有真正執行動畫,那么執行動畫一定在 DefaultItemAnimator 內部;在看其 runPendingAnimations 實現前先大概了解下類的結構;
// mPendingXXX 容器存放將要執行動畫的 ViewHodler private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>(); private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>(); // 這里的 MoveInfo,ChangeInfo 下面解釋 private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>(); private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>(); // mXXXAnimations 容器存放正在執行動畫的 ViewHolder ArrayList<RecyclerView.ViewHolder> mAddAnimations = new ArrayList<>(); ArrayList<RecyclerView.ViewHolder> mMoveAnimations = new ArrayList<>(); ArrayList<RecyclerView.ViewHolder> mRemoveAnimations = new ArrayList<>(); ArrayList<RecyclerView.ViewHolder> mChangeAnimations = new ArrayList<>(); // MoveInfo 額外存儲了執行移除動畫前后的坐標信息用于動畫執行 private static class MoveInfo { public RecyclerView.ViewHolder holder; public int fromX, fromY, toX, toY; // ... } // ChangeInfo 想比于 MoveInfo 額外存儲了 oldHolder private static class ChangeInfo { public RecyclerView.ViewHolder oldHolder, newHolder; public int fromX, fromY, toX, toY; // ... }
runPendingAnimations 方法也在這里實現了,由上面的分析可知 runPendingAnimations 是執行動畫的方法,看一下其實現:
public void runPendingAnimations() { // 標記是否需要執行動畫 就是看看 mPendingXXX 容器是否有數據 boolean removalsPending = !mPendingRemovals.isEmpty(); boolean movesPending = !mPendingMoves.isEmpty(); boolean changesPending = !mPendingChanges.isEmpty(); boolean additionsPending = !mPendingAdditions.isEmpty(); // 如果都無數據 代表不需要執行 if (!removalsPending && !movesPending && !additionsPending && !changesPending) { return; } // 先執行刪除動畫 for (RecyclerView.ViewHolder holder : mPendingRemovals) { // 下面會貼代碼分析... animateRemoveImpl(holder); } // 清空容器 mPendingRemovals.clear(); // 接著執行移動動畫也就是 item 位置變化 if (movesPending) { // 將 mPendingMoves 放入局部變量 moves 并且清空 final ArrayList<MoveInfo> moves = new ArrayList<>(); moves.addAll(mPendingMoves); mMovesList.add(moves); mPendingMoves.clear(); // 構建 Runnable Runnable mover = new Runnable() { @Override public void run() { // 遍歷 moves 執行 animateMoveImpl 方法 for (MoveInfo moveInfo : moves) { // 下面會貼代碼分析... animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, moveInfo.toX, moveInfo.toY); } // 清空容器 moves.clear(); mMovesList.remove(moves); } }; // 如果刪除動畫也需要執行 則延遲執行移動動畫 延遲時間即為 刪除動畫執行需要的時間 if (removalsPending) { View view = moves.get(0).holder.itemView; ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); } else { // 否則就立即執行 mover.run(); } } // 下面的更新動畫、新增動畫邏輯都類似 就不貼了 // ... }
上面的代碼邏輯很簡單,注釋都詳細說明了,就不再解釋了,最后來看看 animateRemoveImpl,animateMoveImpl 方法:
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) { // 拿到 itemView final View view = holder.itemView; // 構建動畫 final ViewPropertyAnimator animation = view.animate(); // 添加到正在執行動畫的容器 mRemoveAnimations.add(holder); // 執行動畫 animation.setDuration(getRemoveDuration()).alpha(0).setListener( new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { // 開始執行動畫回調 // SimpleItemAnimator 中默認空實現 dispatchRemoveStarting(holder); } @Override public void onAnimationEnd(Animator animator) { // 動畫結束后的一些操作 animation.setListener(null); view.setAlpha(1); // 當前 item 動畫執行結束回調 dispatchRemoveFinished(holder); mRemoveAnimations.remove(holder); // 所有動畫執行完成后的回調 // 內部通過判斷上述各個容器是否為空觸發回調 dispatchFinishedWhenDone(); } }).start(); // 執行動畫 } void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; final int deltaX = toX - fromX; final int deltaY = toY - fromY; if (deltaX != 0) { view.animate().translationX(0); } if (deltaY != 0) { view.animate().translationY(0); } final ViewPropertyAnimator animation = view.animate(); // 添加進容器 mMoveAnimations.add(holder); animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { // 動畫開始回調 dispatchMoveStarting(holder); } // ... @Override public void onAnimationEnd(Animator animator) { // 和 animateRemoveImpl 一樣 就不重復說明了 animation.setListener(null); dispatchMoveFinished(holder); mMoveAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); // 執行動畫 }
DefaultItemAnimator 的代碼也不難理解,這里僅僅貼出了重要部分代碼進行解讀,其余代碼的閱讀難度也不大,就不再細說了,對于自定義 ItemAnimator 仿照 DefaultItemAnimator 的邏輯實現即可。
最后
本篇博客到此就結束了,從源碼角度理解了 item 動畫的參數處理以及執行流程,內容跟之前博客關聯性比較強,尤其是對布局相關源碼,可以結合之前的博客一起閱讀。
原文鏈接:https://juejin.cn/post/7169857225949708301
相關推薦
- 2022-09-16 Pandas篩選DataFrame含有空值的數據行的實現_python
- 2022-09-21 python?Flask框架之HTTP請求詳解_python
- 2022-11-03 Python?pandas中apply函數簡介以及用法詳解_python
- 2022-05-31 C?語言的弱符號與弱引用你了解嗎_C 語言
- 2022-03-19 centos7修改網卡后無法上網問題解決過程_Linux
- 2022-11-06 ASP.NET?MVC使用Log4Net記錄異常日志并跳轉到靜態頁_實用技巧
- 2022-08-11 Python操作數據庫之數據庫編程接口_python
- 2022-12-08 Anaconda中pkgs文件夾及如何清空PKGS_相關技巧
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支