欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android 源碼淺析RecyclerView ItemAnimator

 更新時(shí)間:2022年12月21日 17:09:03   作者:孫先森Blog  
這篇文章主要為大家介紹了Android 源碼淺析RecyclerView ItemAnimator,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

在這個(gè)系列博客的第二篇的最后部分提到了預(yù)布局,在預(yù)布局階段,計(jì)算剩余空間時(shí)會(huì)把將要移除的 ViewHolder 忽略,從而計(jì)算出遞補(bǔ)的 ViewHolder,在 ViewHolder 移除、新增、更新時(shí)都可以觸發(fā)默認(rèn)動(dòng)畫(huà)(也可以自定義動(dòng)畫(huà)),那么動(dòng)畫(huà)部分到底是怎么實(shí)現(xiàn)的呢?本篇博客將針對(duì) ItemAnimator 的運(yùn)作流程部分源碼進(jìn)行分析。

源碼分析

前置基礎(chǔ)

一般我們給 RecyclerView 設(shè)置動(dòng)畫(huà)會(huì)調(diào)用 setItemAnimator 方法,直接看一下源碼:

RecyclerView.java

// 默認(rèn)動(dòng)畫(huà) DefaultItemAnimator
ItemAnimator mItemAnimator = new DefaultItemAnimator();
public void setItemAnimator(@Nullable ItemAnimator animator) {
    if (mItemAnimator != null) { // 先結(jié)束動(dòng)畫(huà),取消監(jiān)聽(tīng)
        mItemAnimator.endAnimations();
        mItemAnimator.setListener(null);
    }
    mItemAnimator = animator; // 賦值
    if (mItemAnimator != null) { // 重新設(shè)置監(jiān)聽(tīng)
        mItemAnimator.setListener(mItemAnimatorListener);
    }
}

可以看出 RecyclerView 默認(rèn)提供了 DefaultItemAnimator,先不著急分析它,首先我們要分析出動(dòng)畫(huà)的執(zhí)行流程以及動(dòng)畫(huà)的信息是怎么處理的。先了解以下這么幾個(gè)類(lèi)作為基礎(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)部類(lèi),代碼非常簡(jiǎn)單,向外暴露 setFrom 方法,用于存儲(chǔ) ViewHolder 的位置信息;

InfoRecord

InfoRecord 是 ViewInfoStore 的內(nèi)部類(lèi)(下一小節(jié)分析),代碼也非常簡(jiǎn)單:

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;
    // 這個(gè) flags 要記住?。?
    // 后面多次對(duì)其進(jìn)行賦值,且執(zhí)行動(dòng)畫(huà)時(shí)也根據(jù) flags 來(lái)判斷動(dòng)畫(huà)類(lèi)型;
    int flags;
    // ViewHolder 坐標(biāo)信息
    RecyclerView.ItemAnimator.ItemHolderInfo preInfo; // 預(yù)布局階段的
    RecyclerView.ItemAnimator.ItemHolderInfo postInfo; // 真正布局階段的
    // 池化 提高效率
    static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
    // ...
    // 其內(nèi)部的一些方法都是復(fù)用池相關(guān) 特別簡(jiǎn)單 就不貼了
}

不難看出,InfoRecord 功能和他的名字一樣信息記錄,主要記錄了預(yù)布局、真正布局兩個(gè)階段的 ViewHodler 的位置信息(ItemHolderInfo)。

ViewInfoStore

class ViewInfoStore {
    // 將 ViewHodler 和 InfoRecord 以鍵值對(duì)形式存儲(chǔ)
    final SimpleArrayMap<RecyclerView.ViewHolder, InfoRecord> mLayoutHolderMap =
            new SimpleArrayMap<>();
    // 根據(jù)坐標(biāo)存儲(chǔ) ViewHodler 看名字也看得出是 舊的,舊是指:
    // 1.viewHolder 被隱藏 但 未移除
    // 2.隱藏item被更改
    // 3.預(yù)布局跳過(guò)的 item
    final LongSparseArray<RecyclerView.ViewHolder> mOldChangedHolders = new LongSparseArray<>();
    // mLayoutHolderMap 中添加一項(xiàng) 如果有就改變 InfoRecord 的值
    // 下面很多方法都是類(lèi)似功能 下面的就不貼 if 里面那段了
    void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) { // 沒(méi)有就構(gòu)建一個(gè) 加入 map
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.preInfo = info; 
        record.flags |= FLAG_PRE; // 跟方法名對(duì)應(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);
    }
    // 上面兩個(gè)方法都調(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 獲取對(duì)應(yīng)的 ItemHolderInfo
                info = record.preInfo;
            } else if (flag == FLAG_POST) {
                info = record.postInfo;
            } else {
                throw new IllegalArgumentException("Must provide flag PRE or POST");
            }
            // 如果沒(méi)有包含兩個(gè)階段的flag 直接回收
            if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) {
                mLayoutHolderMap.removeAt(index);
                InfoRecord.recycle(record);
            }
            return info;
        }
        return null;
    }
    // 向 mOldChangedHolders 添加一個(gè) holder
    void addToOldChangeHolders(long key, RecyclerView.ViewHolder holder) {
        mOldChangedHolders.put(key, holder);
    }
    // 和 addToPreLayout 方法類(lèi)似 flags 不同
    void addToAppearedInPreLayoutHolders(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        // ...
        record.flags |= FLAG_APPEAR;
        record.preInfo = info;
    }
    // 和 addToPreLayout 方法類(lèi)似 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;
    }
    // 移除 兩個(gè)容器都移除
    void removeViewHolder(RecyclerView.ViewHolder holder) {
        //...
        mOldChangedHolders.removeAt(i);
        //...
        final InfoRecord info = mLayoutHolderMap.remove(holder);
    }
    // 這里其實(shí)是 動(dòng)畫(huà)開(kāi)始的入口
    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 和 兩個(gè)階段位置信息 進(jìn)行判斷 觸發(fā)對(duì)應(yīng)的 callback 回調(diào)方法
            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                callback.unused(viewHolder);
            } // 一大堆判斷就先省略了,后面會(huì)提到 ...
            // ...
            // 最后回收
            InfoRecord.recycle(record);
        }
    }
    // ...
}

ViewInfoStore,字面翻譯為 View信息商店,類(lèi)名就體現(xiàn)出了他的功能,主要提供了對(duì) ViewHolder 的 InfoRecord 存儲(chǔ)以及修改,并且提供了動(dòng)畫(huà)觸發(fā)的入口。

ProcessCallback

還有最后一個(gè)類(lèi)需要了解,也就是上面 ViewInfoStore 最后一個(gè)方法 process 中用到的 callback,直接看源碼:

ViewInfoStore.java

//前三個(gè)需要做動(dòng)畫(huà)的方法傳入了 viewHolder 以及其預(yù)布局、真正布局兩個(gè)階段的位置信息
interface ProcessCallback {
    // 進(jìn)行消失
    void processDisappeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
    // 進(jìn)行出現(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 有默認(rèn)實(shí)現(xiàn),這個(gè)待會(huì)再詳細(xì)分析,看 callback 的方法名也能略知一二,分別對(duì)應(yīng) ViewHolder 做動(dòng)畫(huà)的幾種情況;

那么從前三個(gè)方法的參數(shù)中也能推斷出,ViewHolder 做動(dòng)畫(huà)時(shí),動(dòng)畫(huà)的數(shù)據(jù)也是從 preInfo 和 postInfo 兩個(gè)參數(shù)中做計(jì)算得出。

動(dòng)畫(huà)處理

前置基礎(chǔ)有點(diǎn)多??????,不過(guò)通過(guò)對(duì)上面幾個(gè)類(lèi)有一些了解,下面在分析動(dòng)畫(huà)觸發(fā)、信息處理時(shí)就不用反復(fù)解釋一些變量的意義了。

dispatchLayoutStep3

在之前分析布局階段的博客中提到 dispatchLayoutStep1、2、3 三個(gè)核心方法,分別對(duì)應(yīng)三種狀態(tài) STEP_START、STEP_LAYOUT、STEP_ANIMATIONS;

很顯然,STEP_ANIMATIONS 是執(zhí)行動(dòng)畫(huà)的階段,再來(lái)看一下 dispatchLayoutStep3 方法中對(duì) item 動(dòng)畫(huà)進(jìn)行了哪些操作:

RecyclerView.java

private void dispatchLayoutStep3() {
    // ...
    if (mState.mRunSimpleAnimations) { // 需要做動(dòng)畫(huà)
        // 倒著循環(huán) 因?yàn)榭赡軙?huì)發(fā)生移除
        for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
            // 獲取到 holder
            ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            if (holder.shouldIgnore()) { // 如果被標(biāo)記忽略則跳過(guò)
                continue;
            }
            // 獲取 holder 的 key 一般情況獲取的就是 position
            long key = getChangedHolderKey(holder);
            // 前置基礎(chǔ)中 提到的 ItemHolderInfo
            // recordPostLayoutInformation 內(nèi)部構(gòu)建了一個(gè) 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í)行消失動(dòng)畫(huà)
                final boolean oldDisappearing = mViewInfoStore.isDisappearing(oldChangeViewHolder);
                final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
                // 這個(gè) if 判斷 將要發(fā)生更新動(dòng)畫(huà)的 vh 已經(jīng)在執(zhí)行消失動(dòng)畫(huà)
                if (oldDisappearing && oldChangeViewHolder == holder) {
                    // 用消失動(dòng)畫(huà)代替
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                } else {
                    // 獲取 預(yù)布局階段的 ItemHolderInfo
                    final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(oldChangeViewHolder);
                    // 設(shè)置 真正布局階段的 ItemHolderInfo
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                    // 然后在取出來(lái)
                    ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
                    if (preInfo == null) {
                        handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
                    } else { // 用上面取出來(lái)的 preInfo postInfo 做更新動(dòng)畫(huà)
                        animateChange(oldChangeViewHolder, holder, preInfo, postInfo,oldDisappearing, newDisappearing);
                    }
                }
            } else { // 沒(méi)有獲取到 直接設(shè)置 hodler 真正布局階段的 位置信息 并且設(shè)置 flag
                mViewInfoStore.addToPostLayout(holder, animationInfo);
            }
        }
        // 開(kāi)始執(zhí)行動(dòng)畫(huà)
        mViewInfoStore.process(mViewInfoProcessCallback);
    }
    // ...
}

代碼中的注釋寫(xiě)的比較詳細(xì),主要注意一下 mViewInfoStore.addToPostLayout 會(huì)給 ViewHolder 生成 InfoRecord 對(duì)象,并且設(shè)置 postInfo,并且給 flags 添加 FLAG_POST,然后以 <ViewHolder, InfoRecord> 鍵值對(duì)形式添加到 ViewInfoStore 的 mLayoutHolderMap 中;

dispatchLayoutStep1

其實(shí)上一小節(jié) dispatchLayoutStep3 方法中也包含對(duì)動(dòng)畫(huà)信息的處理,也就是針對(duì)真正布局后的位置信息設(shè)置的相關(guān)代碼。那么刪除、新增的動(dòng)畫(huà)在哪里實(shí)現(xiàn)呢?首先,回顧一下之前分析的布局流程,真正的布局發(fā)生在 dispatchLayoutStep2 中,預(yù)布局發(fā)生在 dispatchLayoutStep1 中,結(jié)合之前對(duì)預(yù)布局的簡(jiǎn)單解釋,不難理解出預(yù)布局時(shí)肯定也對(duì)動(dòng)畫(huà)信息進(jìn)行了處理,那么直接看一下 dispatchLayoutStep1 的相關(guān)源碼,這部分需要分成兩段來(lái)分析,先看第一段:

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));
            // 忽略、無(wú)效的 跳過(guò)
            if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                continue;
            }
            // 構(gòu)造出 ItemHolderInfo
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPreLayoutInformation(mState, holder,
                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                            holder.getUnmodifiedPayloads());
            // 注意這里 時(shí) addToPreLayout. 表示預(yù)布局階段
            // 此時(shí)設(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ù)雜,記錄當(dāng)前 holder 預(yù)布局階段的位置信息(InfoRecord 的 preInfo)到 ViewInfoStore 的 mLayoutHolderMap 中,且添加了 FLAG_PRE 到 flags 中;

并且如果 holder 發(fā)生改變就添加到 ViewInfoStore 的 mOldChangedHolders 中;

再看下面的代碼:

RecyclerView.java

private void dispatchLayoutStep1() {
    // ...
    if (mState.mRunPredictiveAnimations) {
        // ...
        // 這次是預(yù)布局 計(jì)算可用空間時(shí)忽略了要?jiǎng)h除的項(xiàng)目 所以如果發(fā)生刪除 會(huì)有新的 item 添加進(jìn)去
        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;
            }
            // 這個(gè)判斷也就是沒(méi)有經(jīng)歷過(guò)上一部分代碼的 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) { // 沒(méi)有隱藏 則標(biāo)記在預(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ā)生更新 并且沒(méi)有被移除 就添加到 mOldChangedHolders
                    // 設(shè)置 preInfo 設(shè)置 flag為 FLAG_PRE
                    recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                } else { // 沒(méi)有隱藏的 設(shè)置 flag FLAG_APPEAR, 并且設(shè)置 preInfo
                    mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                }
            }
        }
        clearOldPositions();
    } else {
        clearOldPositions();
    }
    // ...
}

這里結(jié)合之前解釋預(yù)布局時(shí)的圖來(lái)理解下:

第一部分執(zhí)行時(shí),item1、2、3 都會(huì)執(zhí)行 addToPreLayout,addToPreLayout 會(huì)生成 InfoRecord 并且設(shè)置其 preInfo 存儲(chǔ) vh 的位置信息,然后以 <ViewHolder, InfoRecord> 鍵值對(duì)形式添加到 ViewInfoStore 的 mLayoutHolderMap 中;

然后第二部分執(zhí)行了 onLayoutChildren 進(jìn)行了預(yù)布局,以 LinearLayoutManager 為例,在計(jì)算可用空間時(shí)會(huì)忽略要?jiǎng)h除的 item3,從而 item4 被添加到 RecyclerView 中,再次對(duì) child 進(jìn)行遍歷時(shí)進(jìn)行 mViewInfoStore.isInPreLayout(viewHolder) 判斷時(shí)顯然 item4 對(duì)應(yīng)的 ViewHolder 在 mLayoutHolderMap 中獲取為 null,那么就能知道 item4 屬于新增出來(lái)的,就在最后調(diào)用 mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); 生成 InfoRecord 設(shè)置位置信息,并且添加 flag 為 FLAG_APPEAR 添加到 mLayoutHolderMap 中。

總結(jié)

這部分源碼是倒著來(lái)分析的(先看 dispatchLayoutStep3 在看 1 ),可能有點(diǎn)不好理解,先從這三個(gè)布局核心方法的角度來(lái)稍稍總結(jié)一下(均假設(shè)需要執(zhí)行動(dòng)畫(huà)):

dispatchLayoutStep1

  • 首先將當(dāng)前屏幕中的 items 信息保存;(生成 ItemHolderInfo 賦值給 InfoRecord 的 preInfo 并且對(duì)其 flags 添加 FLAG_PRE ,再將 InfoRecord 添加到 ViewInfoStore 的 mLayoutHolderMap 中)
  • 進(jìn)行預(yù)布局;(調(diào)用 LayoutManager 的 onLayoutChildren)
  • 預(yù)布局完成后和第 1 步中保存的信息對(duì)比,將新出現(xiàn)的 item 信息保存;(和第 1 步中不同的是 flags 設(shè)置的是 FLAG_APPEAR)

dispatchLayoutStep2

  • 將預(yù)布局 boolean 值改為 flase;
  • 進(jìn)行真正布局;(調(diào)用 LayoutManager 的 onLayoutChildren)

dispatchLayoutStep3

  • 將真正布局后屏幕上的 items 信息保存;(與 dispatchLayoutStep1 不同的是賦值給 InfoRecord 的 postInfo 并且 flags 添加 FLAG_POST)
  • 執(zhí)行動(dòng)畫(huà),調(diào)用 ViewInfoStore.process;
  • 布局完成回調(diào),onLayoutCompleted;

動(dòng)畫(huà)執(zhí)行

經(jīng)過(guò)上面兩個(gè) dispatchLayoutStep1 和 3 方法的執(zhí)行,ViewInfoStore 中已經(jīng)有預(yù)布局時(shí) item 的信息、真正布局后的 item 信息、以及對(duì)應(yīng)的 flags。最終調(diào)用了 ViewInfoStore 的 process 執(zhí)行動(dòng)畫(huà):

這里面的代碼不難,就是根據(jù) flags 進(jìn)行判斷執(zhí)行對(duì)應(yīng)的動(dòng)畫(huà),調(diào)用的是 ProcessCallback 中的方法進(jìn)行執(zhí)行,那么看一下 ProcessCallback 的具體實(shí)現(xiàn):

RecyclerView.java

private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
        new ViewInfoStore.ProcessCallback() {
            // ...
            // 這里就以執(zhí)行新增動(dòng)畫(huà)為例 其他的也都差不多
            @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) {
    // 先標(biāo)記 vh 不能被回收
    itemHolder.setIsRecyclable(false);
    // mItemAnimator 上面也提過(guò)了 又默認(rèn)實(shí)現(xiàn) 待會(huì)再分析
    if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
        // 這里看方法名也知道是 post 一個(gè) runnable
        postAnimationRunner();
    }
}
void postAnimationRunner() {
    if (!mPostedAnimatorRunner &amp;&amp; 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;
    }
};

整個(gè)調(diào)用下來(lái)核心就在于 ItemAnimator 的兩個(gè)方法調(diào)用(animateAppearance、runPendingAnimations),那么下面我們就來(lái)進(jìn)入 ItemAnimator 的分析;

ItemAnimator

在最開(kāi)始的前置基礎(chǔ)小節(jié)提到 mItemAnimator 實(shí)際上是 DefaultItemAnimator;而 DefaultItemAnimator 繼承自 SimpleItemAnimator,SimpleItemAnimator 又繼承自 ItemAnimator。ItemAnimator 是 RecyclerView 的內(nèi)部類(lèi),其內(nèi)部大部分是抽象方法需要子類(lèi)實(shí)現(xiàn),就簡(jiǎn)單說(shuō)說(shuō)其主要功能不貼代碼了:

  • ItemHolderInfo 是 ItemAnimator 的內(nèi)部類(lèi),用于保存位置信息;
  • ItemAnimatorListener 是其內(nèi)部動(dòng)畫(huà)完成時(shí)的回調(diào)接口;
  • 提供設(shè)置動(dòng)畫(huà)時(shí)間、動(dòng)畫(huà)執(zhí)行、動(dòng)畫(huà)開(kāi)始結(jié)束回調(diào)、動(dòng)畫(huà)狀態(tài)的方法,大部分是需要子類(lèi)實(shí)現(xiàn)的;

而上述提供的 animateAppearance 和 runPendingAnimations 都是抽象方法,這里并沒(méi)有實(shí)現(xiàn);

SimpleItemAnimator

SimpleItemAnimator 繼承自 ItemAnimator,乍一看方法很多,大部分都是空實(shí)現(xiàn)或抽象方法:

這一堆 dispatchXXX 方法和 onXXX 方法是一一對(duì)應(yīng)的,dispatchXXX 中調(diào)用 onXXX,而 onXXX 都是空方法交給子類(lèi)去實(shí)現(xiàn),這部分代碼很簡(jiǎn)單就不貼了;

SimpleItemAnimator 實(shí)現(xiàn)了 animateAppearance:

public boolean animateAppearance(RecyclerView.ViewHolder viewHolder,ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) {
    if (preLayoutInfo != null &amp;&amp; (preLayoutInfo.left != postLayoutInfo.left
            || preLayoutInfo.top != postLayoutInfo.top)) {
        return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
                postLayoutInfo.left, postLayoutInfo.top);
    } else {
        return animateAdd(viewHolder);
    }
}

邏輯很簡(jiǎn)單,如果 preLayoutInfo 不為空,并且 preLayoutInfo 和 postLayoutInfo 的 top、left 不同則調(diào)用 animateMove 否則調(diào)用 animateAdd;看名字也大致能理解是處理移除動(dòng)畫(huà)和添加動(dòng)畫(huà);

對(duì)于 runPendingAnimations SimpleItemAnimator 還是沒(méi)有實(shí)現(xiàn);

DefaultItemAnimator

DefaultItemAnimator 繼承自 SimpleItemAnimator,上述兩個(gè)父類(lèi)中都沒(méi)有真正執(zhí)行動(dòng)畫(huà),那么執(zhí)行動(dòng)畫(huà)一定在 DefaultItemAnimator 內(nèi)部;在看其 runPendingAnimations 實(shí)現(xiàn)前先大概了解下類(lèi)的結(jié)構(gòu);

// mPendingXXX 容器存放將要執(zhí)行動(dòng)畫(huà)的 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í)行動(dòng)畫(huà)的 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 額外存儲(chǔ)了執(zhí)行移除動(dòng)畫(huà)前后的坐標(biāo)信息用于動(dòng)畫(huà)執(zhí)行
private static class MoveInfo {
    public RecyclerView.ViewHolder holder;
    public int fromX, fromY, toX, toY;
    // ...
}
// ChangeInfo 想比于 MoveInfo 額外存儲(chǔ)了 oldHolder
private static class ChangeInfo {
    public RecyclerView.ViewHolder oldHolder, newHolder;
    public int fromX, fromY, toX, toY;
    // ...
}

runPendingAnimations 方法也在這里實(shí)現(xiàn)了,由上面的分析可知 runPendingAnimations 是執(zhí)行動(dòng)畫(huà)的方法,看一下其實(shí)現(xiàn):

public void runPendingAnimations() {
    // 標(biāo)記是否需要執(zhí)行動(dòng)畫(huà) 就是看看 mPendingXXX 容器是否有數(shù)據(jù)
    boolean removalsPending = !mPendingRemovals.isEmpty();
    boolean movesPending = !mPendingMoves.isEmpty();
    boolean changesPending = !mPendingChanges.isEmpty();
    boolean additionsPending = !mPendingAdditions.isEmpty();
    // 如果都無(wú)數(shù)據(jù) 代表不需要執(zhí)行
    if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
        return;
    }
    // 先執(zhí)行刪除動(dòng)畫(huà)
    for (RecyclerView.ViewHolder holder : mPendingRemovals) {
        // 下面會(huì)貼代碼分析...
        animateRemoveImpl(holder);
    }
    // 清空容器
    mPendingRemovals.clear();
    // 接著執(zhí)行移動(dòng)動(dòng)畫(huà)也就是 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) {
                    // 下面會(huì)貼代碼分析...
                    animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                            moveInfo.toX, moveInfo.toY);
                }
                // 清空容器
                moves.clear();
                mMovesList.remove(moves);
            }
        };
        // 如果刪除動(dòng)畫(huà)也需要執(zhí)行 則延遲執(zhí)行移動(dòng)動(dòng)畫(huà) 延遲時(shí)間即為 刪除動(dòng)畫(huà)執(zhí)行需要的時(shí)間
        if (removalsPending) {
            View view = moves.get(0).holder.itemView;
            ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
        } else { // 否則就立即執(zhí)行
            mover.run();
        }
    }
    // 下面的更新動(dòng)畫(huà)、新增動(dòng)畫(huà)邏輯都類(lèi)似 就不貼了
    // ...
}

上面的代碼邏輯很簡(jiǎn)單,注釋都詳細(xì)說(shuō)明了,就不再解釋了,最后來(lái)看看 animateRemoveImpl,animateMoveImpl 方法:

private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
    // 拿到 itemView
    final View view = holder.itemView;
    // 構(gòu)建動(dòng)畫(huà)
    final ViewPropertyAnimator animation = view.animate();
    // 添加到正在執(zhí)行動(dòng)畫(huà)的容器
    mRemoveAnimations.add(holder);
    // 執(zhí)行動(dòng)畫(huà)
    animation.setDuration(getRemoveDuration()).alpha(0).setListener(
            new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animator) {
                    // 開(kāi)始執(zhí)行動(dòng)畫(huà)回調(diào)
                    // SimpleItemAnimator 中默認(rèn)空實(shí)現(xiàn)
                    dispatchRemoveStarting(holder);
                }
                @Override
                public void onAnimationEnd(Animator animator) {
                    // 動(dòng)畫(huà)結(jié)束后的一些操作
                    animation.setListener(null);
                    view.setAlpha(1);
                    // 當(dāng)前 item 動(dòng)畫(huà)執(zhí)行結(jié)束回調(diào)
                    dispatchRemoveFinished(holder);
                    mRemoveAnimations.remove(holder);
                    // 所有動(dòng)畫(huà)執(zhí)行完成后的回調(diào)
                    // 內(nèi)部通過(guò)判斷上述各個(gè)容器是否為空觸發(fā)回調(diào)
                    dispatchFinishedWhenDone();
                }
            }).start(); // 執(zhí)行動(dòng)畫(huà)
}
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();
    // 添加進(jìn)容器
    mMoveAnimations.add(holder);
    animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animator) {
            // 動(dòng)畫(huà)開(kāi)始回調(diào)
            dispatchMoveStarting(holder);
        }
        // ...
        @Override
        public void onAnimationEnd(Animator animator) {
            // 和 animateRemoveImpl 一樣 就不重復(fù)說(shuō)明了
            animation.setListener(null);
            dispatchMoveFinished(holder);
            mMoveAnimations.remove(holder);
            dispatchFinishedWhenDone();
        }
    }).start(); // 執(zhí)行動(dòng)畫(huà)
}

DefaultItemAnimator 的代碼也不難理解,這里僅僅貼出了重要部分代碼進(jìn)行解讀,其余代碼的閱讀難度也不大,就不再細(xì)說(shuō)了,對(duì)于自定義 ItemAnimator 仿照 DefaultItemAnimator 的邏輯實(shí)現(xiàn)即可。

最后

本篇博客到此就結(jié)束了,從源碼角度理解了 item 動(dòng)畫(huà)的參數(shù)處理以及執(zhí)行流程,內(nèi)容跟之前博客關(guān)聯(lián)性比較強(qiáng),尤其是對(duì)布局相關(guān)源碼,可以結(jié)合之前的博客一起閱讀。

以上就是Android 源碼淺析RecyclerView ItemAnimator的詳細(xì)內(nèi)容,更多關(guān)于Android RecyclerView ItemAnimator的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android手勢(shì)密碼實(shí)現(xiàn)實(shí)例代碼

    Android手勢(shì)密碼實(shí)現(xiàn)實(shí)例代碼

    本篇文章主要介紹了Android手勢(shì)密碼實(shí)現(xiàn)實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-04-04
  • Android仿QQ、微信聊天界面長(zhǎng)按提示框效果

    Android仿QQ、微信聊天界面長(zhǎng)按提示框效果

    最近在工作項(xiàng)目中要實(shí)現(xiàn)一個(gè)長(zhǎng)按提示 “復(fù)制” 的功能,類(lèi)似于QQ、微信聊天界面長(zhǎng)按提示框效果,本來(lái)想偷懶在網(wǎng)上找個(gè)開(kāi)源的項(xiàng)目用,但是看了好幾個(gè)都不是很滿意,所以就打算按照自己的思路來(lái)實(shí)現(xiàn)一個(gè)。下面分享給大家,有需要的朋友們可以參考借鑒。
    2016-11-11
  • Android事件分發(fā)機(jī)制(上) ViewGroup的事件分發(fā)

    Android事件分發(fā)機(jī)制(上) ViewGroup的事件分發(fā)

    這篇文章主要為大家詳細(xì)介紹了Android ViewGroup的事件分發(fā)機(jī)制上篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • Android 動(dòng)畫(huà)(View動(dòng)畫(huà),幀動(dòng)畫(huà),屬性動(dòng)畫(huà))詳細(xì)介紹

    Android 動(dòng)畫(huà)(View動(dòng)畫(huà),幀動(dòng)畫(huà),屬性動(dòng)畫(huà))詳細(xì)介紹

    這篇文章主要介紹了Android View動(dòng)畫(huà)、幀動(dòng)畫(huà)和屬性動(dòng)畫(huà)詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • 詳解Android中Room組件的使用

    詳解Android中Room組件的使用

    Room 是在 SQLite 上提供了一個(gè)抽象層,以便在充分利用 SQLite 的強(qiáng)大功能的同時(shí),能夠流暢地訪問(wèn)數(shù)據(jù)庫(kù),這篇文章主要為大家介紹了Room組件的具體使用,需要的可以參考下
    2023-08-08
  • Android studio保存logcat日志到本地的操作

    Android studio保存logcat日志到本地的操作

    這篇文章主要介紹了Android studio保存logcat日志到本地的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-04-04
  • Android RecyclerView的刷新分頁(yè)的實(shí)現(xiàn)

    Android RecyclerView的刷新分頁(yè)的實(shí)現(xiàn)

    這篇文章主要介紹了Android RecyclerView的刷新分頁(yè)的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • android設(shè)備間實(shí)現(xiàn)無(wú)線投屏的示例代碼

    android設(shè)備間實(shí)現(xiàn)無(wú)線投屏的示例代碼

    Android提供了MediaProjection來(lái)實(shí)現(xiàn)錄屏,通過(guò)MediaProjection可以獲取當(dāng)前屏幕的視頻流,而視頻流需要通過(guò)編解碼來(lái)壓縮進(jìn)行傳輸,通過(guò)MediaCodec可實(shí)現(xiàn)視頻的編碼和解碼,這篇文章主要介紹了android設(shè)備間實(shí)現(xiàn)無(wú)線投屏,需要的朋友可以參考下
    2022-06-06
  • Android 仿微信圖像拍攝和選擇界面功能(代碼分享)

    Android 仿微信圖像拍攝和選擇界面功能(代碼分享)

    這篇文章主要介紹了 Android 仿微信圖像拍攝和選擇界面功能,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下
    2017-01-01
  • Android定制自己的EditText輕松改變底線顏色

    Android定制自己的EditText輕松改變底線顏色

    這篇文章主要介紹了Android如何定制自己的EditText,如何輕松改變底線顏色,感興趣的小伙伴們可以參考一下
    2015-12-12

最新評(píng)論