本文繼上篇 ItemDecoration 以後,是深刻理解 RecyclerView 系列的第二篇,關注於 ItemAnimator,主要是分析 RecyclerView Animators 這個庫的原理,而後總結如何本身編寫自定義的 ItemAnimator。本文涉及到的完整代碼能夠在 Github 獲取。java
DefaultItemAnimator
extends SimpleItemAnimator
extends RecyclerView.ItemAnimator
FadeInAnimator
extends BaseItemAnimator
extends SimpleItemAnimator
extends RecyclerView.ItemAnimator
RecyclerView.ItemAnimator
定義了一系列 API 用於開發 item view 的動效
animateDisappearance
, animateAppearance
, animatePersistence
, animateChange
這4個 API 用來對 item view 進行動畫顯示recordPreLayoutInformation
, recordPostLayoutInformation
這2個 API 用來記錄 item view 在 layout 先後的狀態信息,這些信息封裝在 ItemHolderInfo
或者其子類中,並將傳遞給上述4個動畫API中,以便進行動畫展現runPendingAnimations
能夠用來延遲動畫到下一幀,此時就須要在上述4個 API 的實現中返回 true
,而且自行記錄延遲的動畫信息,以便在下一幀時執行dispatchAnimationStarted
和 dispatchAnimationFinished
是用來進行狀態同步和事件通知的,子類必須在動畫開始時調用 dispatchAnimationStarted,結束時調用 dispatchAnimationFinished,固然若是不展現動畫,那就只須要直接調用 dispatchAnimationFinishedSimpleItemAnimator
則對 RecyclerView.ItemAnimator
的 API 進行了一次封裝
animateRemove
, animateAdd
, animateMove
, animateChange
這4個,爲何這樣?這一次封裝就把對 preLayoutInfo 和 postLayoutInfo 的處理的公共代碼封裝了起來,把 ItemHolderInfo 轉換爲了 left, top, x, y 這樣的位置信息,這樣,大部分動畫只須要根據位置變化信息的實現,專一實現本身的動畫邏輯便可,一方面複用了代碼,另外一方面也更好的踐行了單一職責原則RecyclerView.ItemAnimator
的相應動畫 API 了dispatch***
和 on***
API,用於進行事件回調DefaultItemAnimator
是 RecyclerView 包中的一個默認實現,而 BaseItemAnimator
則是 RecyclerView Animators 庫中 animator 的基類,它們都繼承自 SimpleItemAnimator
,二者具備很大類似性,只分析後者BaseItemAnimator 實現了父類的 animateRemove
, animateAdd
, animateMove
, animateChange
這4個 API,而實現方式都是把參數包裝一下,放入相應的 animation 列表中,並返回 true,而後在 runPendingAnimations 函數中集中顯示動畫。爲何要這樣呢?由於 recycler view 的變化是隨時均可能發生的,而這樣的處理就能夠把動畫的顯示按幀對其,即兩幀之間的變化,都在下一幀開始時一塊兒處理。可是這樣作有什麼優點呢?暫時不得而知,DefaultItemAnimator 就是這樣處理的。git
例如 animateRemove 的實現以下:github
@Override public boolean animateRemove(final ViewHolder holder) { endAnimation(holder); preAnimateRemove(holder); mPendingRemovals.add(holder); return true; }
那麼下面重點看看 runPendingAnimations。app
@Override public void runPendingAnimations() { boolean removalsPending = !mPendingRemovals.isEmpty(); boolean movesPending = !mPendingMoves.isEmpty(); boolean changesPending = !mPendingChanges.isEmpty(); boolean additionsPending = !mPendingAdditions.isEmpty(); if (!removalsPending && !movesPending && !additionsPending && !changesPending) { // nothing to animate return; } // First, remove stuff for (ViewHolder holder : mPendingRemovals) { doAnimateRemove(holder); } mPendingRemovals.clear(); // Next, move stuff if (movesPending) { final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>(); moves.addAll(mPendingMoves); mMovesList.add(moves); mPendingMoves.clear(); Runnable mover = new Runnable() { @Override public void run() { 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(); } } // Next, change stuff, to run in parallel with move animations if (