Android 源碼淺析RecyclerView ItemAnimator
前言
在這個系列博客的第二篇的最后部分提到了預(yù)布局,在預(yù)布局階段,計算剩余空間時會把將要移除的 ViewHolder 忽略,從而計算出遞補的 ViewHolder,在 ViewHolder 移除、新增、更新時都可以觸發(fā)默認動畫(也可以自定義動畫),那么動畫部分到底是怎么實現(xiàn)的呢?本篇博客將針對 ItemAnimator 的運作流程部分源碼進行分析。
源碼分析
前置基礎(chǔ)
一般我們給 RecyclerView 設(shè)置動畫會調(diào)用 setItemAnimator 方法,直接看一下源碼:
RecyclerView.java
// 默認動畫 DefaultItemAnimator
ItemAnimator mItemAnimator = new DefaultItemAnimator();
public void setItemAnimator(@Nullable ItemAnimator animator) {
if (mItemAnimator != null) { // 先結(jié)束動畫,取消監(jiān)聽
mItemAnimator.endAnimations();
mItemAnimator.setListener(null);
}
mItemAnimator = animator; // 賦值
if (mItemAnimator != null) { // 重新設(shè)置監(jiān)聽
mItemAnimator.setListener(mItemAnimatorListener);
}
}
可以看出 RecyclerView 默認提供了 DefaultItemAnimator,先不著急分析它,首先我們要分析出動畫的執(zhí)行流程以及動畫的信息是怎么處理的。先了解以下這么幾個類作為基礎(chǔ)。
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 的內(nèi)部類,代碼非常簡單,向外暴露 setFrom 方法,用于存儲 ViewHolder 的位置信息;
InfoRecord
InfoRecord 是 ViewInfoStore 的內(nèi)部類(下一小節(jié)分析),代碼也非常簡單:
ViewInfoStore.java
static class InfoRecord {
// 一些 Flag 定義
static final int FLAG_DISAPPEARED = 1; // 消失
static final int FLAG_APPEAR = 1 << 1; // 出現(xiàn)
static final int FLAG_PRE = 1 << 2; // 預(yù)布局
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 要記?。。?
// 后面多次對其進行賦值,且執(zhí)行動畫時也根據(jù) flags 來判斷動畫類型;
int flags;
// ViewHolder 坐標信息
RecyclerView.ItemAnimator.ItemHolderInfo preInfo; // 預(yù)布局階段的
RecyclerView.ItemAnimator.ItemHolderInfo postInfo; // 真正布局階段的
// 池化 提高效率
static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
// ...
// 其內(nèi)部的一些方法都是復(fù)用池相關(guān) 特別簡單 就不貼了
}
不難看出,InfoRecord 功能和他的名字一樣信息記錄,主要記錄了預(yù)布局、真正布局兩個階段的 ViewHodler 的位置信息(ItemHolderInfo)。
ViewInfoStore
class ViewInfoStore {
// 將 ViewHodler 和 InfoRecord 以鍵值對形式存儲
final SimpleArrayMap<RecyclerView.ViewHolder, InfoRecord> mLayoutHolderMap =
new SimpleArrayMap<>();
// 根據(jù)坐標存儲 ViewHodler 看名字也看得出是 舊的,舊是指:
// 1.viewHolder 被隱藏 但 未移除
// 2.隱藏item被更改
// 3.預(yù)布局跳過的 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) { // 沒有就構(gòu)建一個 加入 map
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.preInfo = info;
record.flags |= FLAG_PRE; // 跟方法名對應(yīng)的 flag
}
// 調(diào)用 popFromLayoutStep 傳遞 FLAG_PRE
RecyclerView.ItemAnimator.ItemHolderInfo popFromPreLayout(RecyclerView.ViewHolder vh) {
return popFromLayoutStep(vh, FLAG_PRE);
}
// 調(diào)用 popFromLayoutStep 傳遞 FLAG_POST
RecyclerView.ItemAnimator.ItemHolderInfo popFromPostLayout(RecyclerView.ViewHolder vh) {
return popFromLayoutStep(vh, FLAG_POST);
}
// 上面兩個方法都調(diào)用的這里 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) { // 根據(jù) flag 獲取對應(yīng)的 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 根據(jù) flag 和 兩個階段位置信息 進行判斷 觸發(fā)對應(yīng)的 callback 回調(diào)方法
if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
callback.unused(viewHolder);
} // 一大堆判斷就先省略了,后面會提到 ...
// ...
// 最后回收
InfoRecord.recycle(record);
}
}
// ...
}
ViewInfoStore,字面翻譯為 View信息商店,類名就體現(xiàn)出了他的功能,主要提供了對 ViewHolder 的 InfoRecord 存儲以及修改,并且提供了動畫觸發(fā)的入口。
ProcessCallback
還有最后一個類需要了解,也就是上面 ViewInfoStore 最后一個方法 process 中用到的 callback,直接看源碼:
ViewInfoStore.java
//前三個需要做動畫的方法傳入了 viewHolder 以及其預(yù)布局、真正布局兩個階段的位置信息
interface ProcessCallback {
// 進行消失
void processDisappeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
// 進行出現(xiàn)
void processAppeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
// 持續(xù) 也就是 不變 或者 數(shù)據(jù)相同大小改變的情況
void processPersistent(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
// 未使用
void unused(RecyclerView.ViewHolder holder);
}
ProcessCallback 在 RecyclerView 有默認實現(xiàn),這個待會再詳細分析,看 callback 的方法名也能略知一二,分別對應(yīng) ViewHolder 做動畫的幾種情況;
那么從前三個方法的參數(shù)中也能推斷出,ViewHolder 做動畫時,動畫的數(shù)據(jù)也是從 preInfo 和 postInfo 兩個參數(shù)中做計算得出。
動畫處理
前置基礎(chǔ)有點多??????,不過通過對上面幾個類有一些了解,下面在分析動畫觸發(fā)、信息處理時就不用反復(fù)解釋一些變量的意義了。
dispatchLayoutStep3
在之前分析布局階段的博客中提到 dispatchLayoutStep1、2、3 三個核心方法,分別對應(yīng)三種狀態(tài) STEP_START、STEP_LAYOUT、STEP_ANIMATIONS;
很顯然,STEP_ANIMATIONS 是執(zhí)行動畫的階段,再來看一下 dispatchLayoutStep3 方法中對 item 動畫進行了哪些操作:
RecyclerView.java
private void dispatchLayoutStep3() {
// ...
if (mState.mRunSimpleAnimations) { // 需要做動畫
// 倒著循環(huán) 因為可能會發(fā)生移除
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);
// 前置基礎(chǔ)中 提到的 ItemHolderInfo
// recordPostLayoutInformation 內(nèi)部構(gòu)建了一個 ItemHolderInfo 并且調(diào)用了 setFrom 設(shè)置了 位置信息
final ItemHolderInfo animationInfo = mItemAnimator.recordPostLayoutInformation(mState, holder);
// 從 ViewInfoStore 的 mOldChangedHolders 中獲取 vh
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
// 是否正在執(zhí)行消失動畫
final boolean oldDisappearing = mViewInfoStore.isDisappearing(oldChangeViewHolder);
final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
// 這個 if 判斷 將要發(fā)生更新動畫的 vh 已經(jīng)在執(zhí)行消失動畫
if (oldDisappearing && oldChangeViewHolder == holder) {
// 用消失動畫代替
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
// 獲取 預(yù)布局階段的 ItemHolderInfo
final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(oldChangeViewHolder);
// 設(shè)置 真正布局階段的 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 { // 沒有獲取到 直接設(shè)置 hodler 真正布局階段的 位置信息 并且設(shè)置 flag
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// 開始執(zhí)行動畫
mViewInfoStore.process(mViewInfoProcessCallback);
}
// ...
}
代碼中的注釋寫的比較詳細,主要注意一下 mViewInfoStore.addToPostLayout 會給 ViewHolder 生成 InfoRecord 對象,并且設(shè)置 postInfo,并且給 flags 添加 FLAG_POST,然后以 <ViewHolder, InfoRecord> 鍵值對形式添加到 ViewInfoStore 的 mLayoutHolderMap 中;
dispatchLayoutStep1
其實上一小節(jié) dispatchLayoutStep3 方法中也包含對動畫信息的處理,也就是針對真正布局后的位置信息設(shè)置的相關(guān)代碼。那么刪除、新增的動畫在哪里實現(xiàn)呢?首先,回顧一下之前分析的布局流程,真正的布局發(fā)生在 dispatchLayoutStep2 中,預(yù)布局發(fā)生在 dispatchLayoutStep1 中,結(jié)合之前對預(yù)布局的簡單解釋,不難理解出預(yù)布局時肯定也對動畫信息進行了處理,那么直接看一下 dispatchLayoutStep1 的相關(guān)源碼,這部分需要分成兩段來分析,先看第一段:
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;
}
// 構(gòu)造出 ItemHolderInfo
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
// 注意這里 時 addToPreLayout. 表示預(yù)布局階段
// 此時設(shè)置的是 InfoRecord 的 preInfo,flag 是 FLAG_PRE
mViewInfoStore.addToPreLayout(holder, animationInfo);
// 如果 holder 發(fā)生改變 添加到 ViewInfoStore 的 mOldChangedHolders 中
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder); // 獲取 key 一般是 position
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
// ...
}
這一段也不復(fù)雜,記錄當前 holder 預(yù)布局階段的位置信息(InfoRecord 的 preInfo)到 ViewInfoStore 的 mLayoutHolderMap 中,且添加了 FLAG_PRE 到 flags 中;
并且如果 holder 發(fā)生改變就添加到 ViewInfoStore 的 mOldChangedHolders 中;
再看下面的代碼:
RecyclerView.java
private void dispatchLayoutStep1() {
// ...
if (mState.mRunPredictiveAnimations) {
// ...
// 這次是預(yù)布局 計算可用空間時忽略了要刪除的項目 所以如果發(fā)生刪除 會有新的 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;
}
// 這個判斷也就是沒有經(jīng)歷過上一部分代碼的 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) { // 沒有隱藏 則標記在預(yù)布局階段出現(xiàn)
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
// 構(gòu)造出 ItemHolderInfo
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
if (wasHidden) {
// 隱藏的 如果發(fā)生更新 并且沒有被移除 就添加到 mOldChangedHolders
// 設(shè)置 preInfo 設(shè)置 flag為 FLAG_PRE
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else { // 沒有隱藏的 設(shè)置 flag FLAG_APPEAR, 并且設(shè)置 preInfo
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
clearOldPositions();
} else {
clearOldPositions();
}
// ...
}
這里結(jié)合之前解釋預(yù)布局時的圖來理解下:

第一部分執(zhí)行時,item1、2、3 都會執(zhí)行 addToPreLayout,addToPreLayout 會生成 InfoRecord 并且設(shè)置其 preInfo 存儲 vh 的位置信息,然后以 <ViewHolder, InfoRecord> 鍵值對形式添加到 ViewInfoStore 的 mLayoutHolderMap 中;
然后第二部分執(zhí)行了 onLayoutChildren 進行了預(yù)布局,以 LinearLayoutManager 為例,在計算可用空間時會忽略要刪除的 item3,從而 item4 被添加到 RecyclerView 中,再次對 child 進行遍歷時進行 mViewInfoStore.isInPreLayout(viewHolder) 判斷時顯然 item4 對應(yīng)的 ViewHolder 在 mLayoutHolderMap 中獲取為 null,那么就能知道 item4 屬于新增出來的,就在最后調(diào)用 mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); 生成 InfoRecord 設(shè)置位置信息,并且添加 flag 為 FLAG_APPEAR 添加到 mLayoutHolderMap 中。
總結(jié)
這部分源碼是倒著來分析的(先看 dispatchLayoutStep3 在看 1 ),可能有點不好理解,先從這三個布局核心方法的角度來稍稍總結(jié)一下(均假設(shè)需要執(zhí)行動畫):
dispatchLayoutStep1
- 首先將當前屏幕中的 items 信息保存;(生成 ItemHolderInfo 賦值給 InfoRecord 的 preInfo 并且對其 flags 添加 FLAG_PRE ,再將 InfoRecord 添加到 ViewInfoStore 的 mLayoutHolderMap 中)
- 進行預(yù)布局;(調(diào)用 LayoutManager 的 onLayoutChildren)
- 預(yù)布局完成后和第 1 步中保存的信息對比,將新出現(xiàn)的 item 信息保存;(和第 1 步中不同的是 flags 設(shè)置的是 FLAG_APPEAR)
dispatchLayoutStep2
- 將預(yù)布局 boolean 值改為 flase;
- 進行真正布局;(調(diào)用 LayoutManager 的 onLayoutChildren)
dispatchLayoutStep3
- 將真正布局后屏幕上的 items 信息保存;(與 dispatchLayoutStep1 不同的是賦值給 InfoRecord 的 postInfo 并且 flags 添加 FLAG_POST)
- 執(zhí)行動畫,調(diào)用 ViewInfoStore.process;
- 布局完成回調(diào),onLayoutCompleted;
動畫執(zhí)行
經(jīng)過上面兩個 dispatchLayoutStep1 和 3 方法的執(zhí)行,ViewInfoStore 中已經(jīng)有預(yù)布局時 item 的信息、真正布局后的 item 信息、以及對應(yīng)的 flags。最終調(diào)用了 ViewInfoStore 的 process 執(zhí)行動畫:

這里面的代碼不難,就是根據(jù) flags 進行判斷執(zhí)行對應(yīng)的動畫,調(diào)用的是 ProcessCallback 中的方法進行執(zhí)行,那么看一下 ProcessCallback 的具體實現(xiàn):
RecyclerView.java
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
// ...
// 這里就以執(zhí)行新增動畫為例 其他的也都差不多
@Override
public void processAppeared(ViewHolder viewHolder,
ItemHolderInfo preInfo, ItemHolderInfo info) {
// 調(diào)用 animateAppearance
animateAppearance(viewHolder, preInfo, info);
}
// ...
};
void animateAppearance(@NonNull ViewHolder itemHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
// 先標記 vh 不能被回收
itemHolder.setIsRecyclable(false);
// mItemAnimator 上面也提過了 又默認實現(xiàn) 待會再分析
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) {
// 有調(diào)用到了 mItemAnimator 中
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
};
整個調(diào)用下來核心就在于 ItemAnimator 的兩個方法調(diào)用(animateAppearance、runPendingAnimations),那么下面我們就來進入 ItemAnimator 的分析;
ItemAnimator
在最開始的前置基礎(chǔ)小節(jié)提到 mItemAnimator 實際上是 DefaultItemAnimator;而 DefaultItemAnimator 繼承自 SimpleItemAnimator,SimpleItemAnimator 又繼承自 ItemAnimator。ItemAnimator 是 RecyclerView 的內(nèi)部類,其內(nèi)部大部分是抽象方法需要子類實現(xiàn),就簡單說說其主要功能不貼代碼了:
- ItemHolderInfo 是 ItemAnimator 的內(nèi)部類,用于保存位置信息;
- ItemAnimatorListener 是其內(nèi)部動畫完成時的回調(diào)接口;
- 提供設(shè)置動畫時間、動畫執(zhí)行、動畫開始結(jié)束回調(diào)、動畫狀態(tài)的方法,大部分是需要子類實現(xiàn)的;
而上述提供的 animateAppearance 和 runPendingAnimations 都是抽象方法,這里并沒有實現(xiàn);
SimpleItemAnimator
SimpleItemAnimator 繼承自 ItemAnimator,乍一看方法很多,大部分都是空實現(xiàn)或抽象方法:

這一堆 dispatchXXX 方法和 onXXX 方法是一一對應(yīng)的,dispatchXXX 中調(diào)用 onXXX,而 onXXX 都是空方法交給子類去實現(xiàn),這部分代碼很簡單就不貼了;
SimpleItemAnimator 實現(xiàn)了 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 不同則調(diào)用 animateMove 否則調(diào)用 animateAdd;看名字也大致能理解是處理移除動畫和添加動畫;
對于 runPendingAnimations SimpleItemAnimator 還是沒有實現(xiàn);
DefaultItemAnimator
DefaultItemAnimator 繼承自 SimpleItemAnimator,上述兩個父類中都沒有真正執(zhí)行動畫,那么執(zhí)行動畫一定在 DefaultItemAnimator 內(nèi)部;在看其 runPendingAnimations 實現(xiàn)前先大概了解下類的結(jié)構(gòu);
// mPendingXXX 容器存放將要執(zhí)行動畫的 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 容器存放正在執(zhí)行動畫的 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 額外存儲了執(zhí)行移除動畫前后的坐標信息用于動畫執(zhí)行
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 方法也在這里實現(xiàn)了,由上面的分析可知 runPendingAnimations 是執(zhí)行動畫的方法,看一下其實現(xiàn):
public void runPendingAnimations() {
// 標記是否需要執(zhí)行動畫 就是看看 mPendingXXX 容器是否有數(shù)據(jù)
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
// 如果都無數(shù)據(jù) 代表不需要執(zhí)行
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
return;
}
// 先執(zhí)行刪除動畫
for (RecyclerView.ViewHolder holder : mPendingRemovals) {
// 下面會貼代碼分析...
animateRemoveImpl(holder);
}
// 清空容器
mPendingRemovals.clear();
// 接著執(zhí)行移動動畫也就是 item 位置變化
if (movesPending) {
// 將 mPendingMoves 放入局部變量 moves 并且清空
final ArrayList<MoveInfo> moves = new ArrayList<>();
moves.addAll(mPendingMoves);
mMovesList.add(moves);
mPendingMoves.clear();
// 構(gòu)建 Runnable
Runnable mover = new Runnable() {
@Override
public void run() {
// 遍歷 moves 執(zhí)行 animateMoveImpl 方法
for (MoveInfo moveInfo : moves) {
// 下面會貼代碼分析...
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
moveInfo.toX, moveInfo.toY);
}
// 清空容器
moves.clear();
mMovesList.remove(moves);
}
};
// 如果刪除動畫也需要執(zhí)行 則延遲執(zhí)行移動動畫 延遲時間即為 刪除動畫執(zhí)行需要的時間
if (removalsPending) {
View view = moves.get(0).holder.itemView;
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
} else { // 否則就立即執(zhí)行
mover.run();
}
}
// 下面的更新動畫、新增動畫邏輯都類似 就不貼了
// ...
}
上面的代碼邏輯很簡單,注釋都詳細說明了,就不再解釋了,最后來看看 animateRemoveImpl,animateMoveImpl 方法:
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
// 拿到 itemView
final View view = holder.itemView;
// 構(gòu)建動畫
final ViewPropertyAnimator animation = view.animate();
// 添加到正在執(zhí)行動畫的容器
mRemoveAnimations.add(holder);
// 執(zhí)行動畫
animation.setDuration(getRemoveDuration()).alpha(0).setListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
// 開始執(zhí)行動畫回調(diào)
// SimpleItemAnimator 中默認空實現(xiàn)
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(Animator animator) {
// 動畫結(jié)束后的一些操作
animation.setListener(null);
view.setAlpha(1);
// 當前 item 動畫執(zhí)行結(jié)束回調(diào)
dispatchRemoveFinished(holder);
mRemoveAnimations.remove(holder);
// 所有動畫執(zhí)行完成后的回調(diào)
// 內(nèi)部通過判斷上述各個容器是否為空觸發(fā)回調(diào)
dispatchFinishedWhenDone();
}
}).start(); // 執(zhí)行動畫
}
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) {
// 動畫開始回調(diào)
dispatchMoveStarting(holder);
}
// ...
@Override
public void onAnimationEnd(Animator animator) {
// 和 animateRemoveImpl 一樣 就不重復(fù)說明了
animation.setListener(null);
dispatchMoveFinished(holder);
mMoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start(); // 執(zhí)行動畫
}
DefaultItemAnimator 的代碼也不難理解,這里僅僅貼出了重要部分代碼進行解讀,其余代碼的閱讀難度也不大,就不再細說了,對于自定義 ItemAnimator 仿照 DefaultItemAnimator 的邏輯實現(xiàn)即可。
最后
本篇博客到此就結(jié)束了,從源碼角度理解了 item 動畫的參數(shù)處理以及執(zhí)行流程,內(nèi)容跟之前博客關(guān)聯(lián)性比較強,尤其是對布局相關(guān)源碼,可以結(jié)合之前的博客一起閱讀。
以上就是Android 源碼淺析RecyclerView ItemAnimator的詳細內(nèi)容,更多關(guān)于Android RecyclerView ItemAnimator的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Android開發(fā)數(shù)據(jù)持久化之文件存儲(附源碼)
本篇文章主要介紹了詳解Android開發(fā)數(shù)據(jù)持久化之文件存儲(附源碼),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
Android采取BroadcastReceiver方式自動獲取驗證碼
這篇文章主要介紹了Android采取BroadcastReceiver方式自動獲取驗證碼,感興趣的小伙伴們可以參考一下2016-08-08
Android Notification使用方法總結(jié)
這篇文章主要介紹了Android Notification使用方法總結(jié)的相關(guān)資料,這里提供了四種使用方法,需要的朋友可以參考下2017-09-09
系統(tǒng)應(yīng)用根據(jù)Uri授予權(quán)限方法詳解
這篇文章主要為大家介紹了系統(tǒng)應(yīng)用根據(jù)Uri授予權(quán)限方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
Android編程連接MongoDB及增刪改查等基本操作示例
這篇文章主要介紹了Android編程連接MongoDB及增刪改查等基本操作,簡單介紹了MongoDB功能、概念、使用方法及Android操作MongoDB數(shù)據(jù)庫的基本技巧,需要的朋友可以參考下2017-07-07
MT6589平臺通話錄音時播放提示音給對方功能的具體實現(xiàn)
MT6589平臺通話錄音時如何播放提示音給對方,可以通過修改以下文件即可,希望對你有所幫助2013-06-06
Android自定義SeekBar實現(xiàn)滑動驗證且不可點擊
這篇文章主要為大家詳細介紹了Android自定義SeekBar實現(xiàn)滑動驗證且不可點擊,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-03-03

