抽絲剝繭RecyclerView - ItemAnimator & Adapter

封面

前言

抽絲剝繭 RecyclerView系列文章的目的在于幫助Android開發者提高對RecyclerView的認知,本文是整個系列的第三篇。

在前面的系列文章中,我們從源碼的角度分別研究了:

  • RecyclerView(整體結構)
  • Recycler
  • LayoutManger
  • ItemDecoration(略微了解)

縱觀RecyclerView,似乎還剩下ItemAnimatorAdapter,那么本文作為抽絲剝繭RecyclerView系列的最后一篇,自然要將剩下的部分全部分析完畢(文末有往期文章的鏈接)。

目錄

目錄

一、RecyclerView中的魔法師 - Adapter

我將Adapter稱為RecyclerView中的魔法師,為什么叫它魔法師呢?因為它將數據變成了具體的視圖,不過這也是我們平時談論頗多的適配器模式

Adapter的主要功能是數據轉子視圖數據管理及通知,所以在了解源碼之前,我們還需了解Adpater的相關類:

名稱 作用
AdapterDataObservable 數據發生變化的時候實際處理的類
ViewHolder 存入子視圖和當前的位置信息,大家應該都很熟悉了~

1. 數據轉子視圖

在之前的文章《抽絲剝繭RecyclerView - 化整為零》我們介紹Recycler的時候,已經了解到在Recycler如果沒有緩存ViewHolder,會調用Adapter#onCreateViewHolder創建一個ViewHolder,我們平常在該方法的實現中,通常會:

View root = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.xxx,viewGroup,false);
return new ViewHolder(root);

以這樣的方式創建子視圖,創建完的子視圖會交給ViewHolder管理,存儲在ViewHolder中的itemView,接著Recycler會調用Adapter#onBindViewHolder實現將數據展示在控件中,不過這兩個方法都是由控件的使用者實現。

2. 數據管理

每次數據發生變化的時候,我們都需要調用Adapter#notifyxxx通知RecyclerView數據集發生了變化。這次我們以刪除為例來分析源碼。

2.1 設置適配器

設置適配器的代碼是RecyclerView#setAdapter

public void setAdapter(Adapter adapter) {
    // ...
    // 重點方法
    setAdapterInternal(adapter, false, true);
    // ...
}

private void setAdapterInternal(Adapter adapter, Boolean compatibleWithPrevious,
            Boolean removeAndRecycleViews) {
    if (mAdapter != null) {
        // 舊的適配器解除注冊
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    // ...
    final Adapter oldAdapter = mAdapter;
    // 對新的適配器檢測數據監聽
    mAdapter = adapter;
    if (adapter != null) {
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    // ...
}

該代碼主要作用有兩點:

  • 舊的適配器取消注冊
  • 注冊新的適配器中的數據變化的通知對象

數據變化通知對象是這個mObserver,來看看這個mObserver是什么:

private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

// 數據發生變化的回調接口
// 通知到RecyclerView中
// RecyclerViewDataObserver繼承自AdapterDataObserver
public abstract static class AdapterDataObserver {
    public void onChanged() {
        // Do nothing
    }
    public void onItemRangeChanged(int positionStart, int itemCount) {
        // do nothing
    }
    public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
        // fallback to onItemRangeChanged(positionStart, itemCount) if app
        // does not override this method.
        onItemRangeChanged(positionStart, itemCount);
    }
    public void onItemRangeInserted(int positionStart, int itemCount) {
        // do nothing
    }
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        // do nothing
    }
    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        // do nothing
    }
}

而這個RecyclerViewDataObserver則是繼承自AdapterDataObserver抽象類,具體的實現細節我們后面再討論。

2.2 數據刪除

使用場景是這樣的:


RecyclerView刪除動畫.gif

點擊界面中的一個刪除按鈕,刪除數據列表中的第一個數據,然后使用適配器通知數據中已經刪除:

btnDeleteOne.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        List<String> strings = mAdapter.getValues();
        if(strings.size() == 0)
        return;
        
        // 移除第一個數據
        strings.remove(0);
        // 適配器通知刪除
        mAdapter.notifyItemRemoved(0);
}
2.3 適配器通知

這里有必要說明一下:AdapterRecyclerViewDataObserver都是RecyclerView的內部類,所以它們可以直接使用RecyclerView內部的資源。

RecyclerView中的數據刪除的時候,我們調用了Adapter#notifyRemoved方法:

public final void notifyItemRemoved(int position) {
    mObservable.notifyItemRangeRemoved(position, 1);
}

發現刪除的處理交給了上面介紹的mObservable,我們來看一下RecyclerViewDataObserver#notifyItemRemoved具體實現:

private class RecyclerViewDataObserver extends AdapterDataObserver {
    //... 省略一些方法

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    // 刷新界面或者直接進行動畫
    // 刪除這里是調用的刷新界面
    void triggerUpdateProcessor() {
        if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
            ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
        } else {
            mAdapterUpdateDuringMeasure = true;
            requestLayout();
        }
    }
}

RecyclerViewDataObserver的通知刪除方法中,它又把刪除的處理交給了AdapterHelper,調用了AdapterHelper#onItemRangeRemoved

/**
 * @return True if updates should be processed.
 */
Boolean onItemRangeRemoved(int positionStart, int itemCount) {
    if (itemCount < 1) {
        return false;
    }
    // mPendingUpdates是List<UpdateOp>
    // 這里是將一個刪除的UpdateOp加入mPendingUpdates中
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
    mExistingUpdateTypes |= UpdateOp.REMOVE;
    return mPendingUpdates.size() == 1;
}

AdapterHelper對自己比較有信心,沒有交給別人處理,他在自己的mPendingUpdates中加入一個刪除標記的UpdateOp,這個mPendingUpdates有什么作用呢?我們同樣在使用的時候介紹。回到RecyclerViewDataObserver中的RecyclerViewDataObserver#notifyItemRemoved,調用完AdapterHelper#onItemRangeRemoved之后,它立馬又調用了requestLayout進行界面刷新。

目錄.png

2.4 界面繪制流程的一些細節
界面繪制一直是我們之前博客的重點討論對象,本章我們就數據通知再看一下關于數據通知的細節。

RecyclerView#dispatchLayoutStep1方法中,RecyclerView會調用RecyclerView#processAdapterUpdatesAndSetAnimationFlags處理Adapter中的更新和為動畫設置標記,這里我們只看適配器數據更新相關:

private void processAdapterUpdatesAndSetAnimationFlags() {
    //...
  
    // simple animations are a subset of advanced animations (which will cause a
    // pre-layout step)
    // If layout supports predictive animations, pre-process to decide if we want to run them
    if (predictiveItemAnimationsEnabled()) {
        mAdapterHelper.preProcess();
    } else {
        mAdapterHelper.consumeUpdatesInOnePass();
    }
  
    // ...
}

private Boolean predictiveItemAnimationsEnabled() {
    // RecyclerView設置了默認的mItemAnimator,
    // 以及LinearLayout的supportsPredictiveItemAnimations()為true
    // 該方法返回為true
    return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
}

由于RecyclerView#predictiveItemAnimationsEnabled通常會返回true,那我們跳到AdapterHelper,查看AdapterHelper#preProcess方法:

void preProcess() {
    // ...
    final int count = mPendingUpdates.size();
    for (int i = 0; i < count; i++) {
        UpdateOp op = mPendingUpdates.get(i);
        switch (op.cmd) {
            // ... 添加省略

            case UpdateOp.REMOVE:
                applyRemove(op);
                break;

            // 更新、移動標簽省略
        }
    }
    mPendingUpdates.clear();
}

mPendingUpdates是一個ArrayList<UpdateOp>,上述方法就是消費我們在之前添加進mPendingUpdates的刪除UpdateOp,在處理刪除屬性的UpdateOpAdapterHelper#applyRemove方法中又調用AdapterHelper#postponeAndUpdateViewHolders

private void postponeAndUpdateViewHolders(UpdateOp op) {
    mPostponedList.add(op);
    switch (op.cmd) {
        // ... 省略添加、更新、移動

        case UpdateOp.REMOVE:
            mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,op.itemCount);
            break;
        default:
            throw new IllegalArgumentException("Unknown update op type for " + op);
    }
}

真實的處理交給了AdapterHelper中的mCallback,而mCallback的實現同樣也在RecyclerView,那我們直接查看mCallback的具體實現:

void initAdapterManager() {
    mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
        // ...省略其他方法
                                       
        // 僅僅展示刪除的方法
        @Override
        public void offsetPositionsForRemovingLaidOutOrNewView(
                            int positionStart, int itemCount) {
            offsetPositionRecordsForRemove(positionStart, itemCount, false);
            mItemsAddedOrRemoved = true;
        }
        //... 省略其他方法
    });
}

void offsetPositionRecordsForRemove(int positionStart, int itemCount,
            Boolean applyToPreLayout) {
    final int positionEnd = positionStart + itemCount;
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderint(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.shouldIgnore()) {
            if (holder.mPosition >= positionEnd) {
                // 更新未刪除的ViewHOlder的的位置信息
                holder.offsetPosition(-itemCount, applyToPreLayout);
                mState.mStructureChanged = true;
            } else if (holder.mPosition >= positionStart) {
                // 跟新要刪除逇ViewHolder的位置信息
                holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount,
                                            applyToPreLayout);
                mState.mStructureChanged = true;
            }
        }
    }
    mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
    requestLayout();
}

上述代碼的作用主要有兩點:

  • 對于要刪除的ViewHolder加上刪除的flag,更新ViewHolder的位置
  • 對于位置會變化的ViewHolder則更新位置

在數據刪除以后,Adapter的作用就是為這些變化的ViewHolder添加刪除標簽和更新位置信息,后續的處理就交給了LayoutManagerItemAnimator,我們在下面的動畫中分析~

二、界面交互的粘合劑 - ItemAnimator

好的動畫會讓界面的交互很自然,RecyclerView作為一款強大的UI控件,自然也是支持動畫的,沒錯,RecyclerView子視圖動畫是由ItemAnimator實現的。

上文中的Gif不適合講解,于是我換了一張聊天圖,同樣要刪除第一條信息:

開始圖片

上文中,我們討論Adapter的結果是它更新了ViewHolder的一些flag,那么這些有了flagViewHolder是如何處理的呢?

在此之前,簡單了解一下動畫相關類ViewInfoStore

ViewInfoStore

1. 進行預布局

預布局是什么呢?簡單來說,RecyclerView進行真實的布局之前,提前進行一次布局,也就是說,LayoutManager#onLayoutChildren方法會執行兩次,那么為什么會執行兩次呢?我們慢慢分析。

預布局是一個很重要得過程,當有簡單的子視圖動畫發生的時候,它就會被觸發,這一點我們得回顧一下RecyclerView#dispatchLayoutStep1方法,直接進入其中的RecyclerView#processAdapterUpdatesAndSetAnimationFlags方法:

private void processAdapterUpdatesAndSetAnimationFlags() {
    // simple animations are a subset of advanced animations (which will cause a
    // pre-layout step)
    // If layout supports predictive animations, pre-process to decide if we want to run them
    if (predictiveItemAnimationsEnabled()) {
        mAdapterHelper.preProcess();
    } else {
        mAdapterHelper.consumeUpdatesInOnePass();
    }
  
    Boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
    // mFirstLayoutComplete會在第一次布局完成以后設置為true
    mState.mRunSimpleAnimations = mFirstLayoutComplete
                    && mItemAnimator != null
                    && (mDataSetHasChangedAfterLayout
                    || animationTypeSupported
                    || mLayout.mRequestedSimpleAnimations)
                    && (!mDataSetHasChangedAfterLayout
                    || mAdapter.hasStableIds());
    mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
                    && animationTypeSupported
                    && !mDataSetHasChangedAfterLayout
                    && predictiveItemAnimationsEnabled();
}

從上面的代碼中,我們可以看出:

  • 第一段注釋中表明,簡單的動畫會觸發預布局
  • RecyclerView第一次布局完成以后才有資格觸發動畫,mFirstLayoutComplete是在第一次布局完成以后設置為true
  • mState.mRunSimpleAnimationstruemState.mRunPredictiveAnimationstrue的充要條件,mState.mRunPredictiveAnimations這個屬性很重要,由它決定是否進行預布局

重新回到RecyclerView#dispatchLayoutStep1方法:

private void dispatchLayoutStep1() {
    // ... 
    mViewInfoStore.clear();
    processAdapterUpdatesAndSetAnimationFlags();
    // 重置一些狀態
    // ... 省略
    mItemsAddedOrRemoved = mItemsChanged = false;
    // 是否預布局取決于mState.mRunPredictiveAnimations
    mState.mInPreLayout = mState.mRunPredictiveAnimations;
    if (mState.mRunSimpleAnimations) {
        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;
            }
            // 記錄當前的位置信息 Left、Right、Top、Bottom等
            final ItemHolderInfo animationInfo = mItemAnimator
                                    .recordPreLayoutInformation(mState, holder,
                                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                            holder.getUnmodifiedPayloads());
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            // ... 省略
        }
    }
    if (mState.mRunPredictiveAnimations) {
        // Step 1: run prelayout: This will use the old positions of items. The layout manager
        // is expected to layout everything, even removed items (though not to add removed
        // items back to the container). This gives the pre-layout position of APPEARING views
        // which come into existence as part of the real layout.
        // 大致就是layoutManager會layout每一個子視圖,包括后面加入的子視圖和刪除的子視圖,這樣以后,layoutManager就很清楚
        // 要執行哪些動畫了
        saveOldPositions();
        final Boolean didStructureChange = mState.mStructureChanged;
        mState.mStructureChanged = false;
        // temporarily disable flag because we are asking for previous layout
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = didStructureChange;
        for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
            final View child = mChildHelper.getChildAt(i);
            final ViewHolder viewHolder = getChildViewHolderint(child);
            // ...
            if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                // 對于新出來的ViewHolder添加標簽
                // ... 省略一些方法
                mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                
            }
        }
        // we don't process disappearing list because they may re-appear in post layout pass.
        clearOldPositions();
    } else {
        clearOldPositions();
    }
    // ...
}

除了上面直接進入的方法,還有兩個if語句。

第一個if語句:mState.mRunSimpleAnimations為true

這個內容很簡單,對預布局前存在的ViewHolder的的位置信息進行記錄。

第二個if語句:mState.mRunPredictiveAnimations為true

第二個if語句的內容就復雜多了,首先會進行預布局過程,該過程第一次調用了LayoutManager#onLayoutChildren,關于布局的具體過程,這里我就不講解了,想要了解的同學可以翻閱我之前的文章:《抽絲剝繭RecyclerView - LayoutManager》

需要指出的是,在添加子視圖中,調用了LayoutManager#addViewInt方法:

private void addViewint(View child, int index, Boolean disappearing) {
    // ...
    if (disappearing || holder.isRemoved()) {
        // these views will be hidden at the end of the layout pass.
        mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
    } else {
        // This may look like unnecessary but may happen if layout manager supports
        // predictive layouts and adapter removed then re-added the same item.
        // In this case, added version will be visible in the post layout (because add is
        // deferred) but RV will still bind it to the same View.
        // So if a View re-appears in post layout pass, remove it from disappearing list.
        mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
    }
    // ...
}

該方法的目的是如果是被刪除的ViewHolder,它會為ViewInfoStoreViewHolder對應的記錄InfoRecord添加已刪除的標記,在真實的布局(非預布局)中,被刪除的ViewHolder是不會被使用的,所以說,只有預布局才會記錄刪除動畫

預布局完成,界面的樣子:
預布局

可以看到,第一次布局完了以后,需要刪除的ViewHolder和自動填充的ViewHolder都被加入了RecyclerView,不過,RecyclerView#DispatchLayoutStep1還沒結束,它會調用ViewInfoStore會給新加入的ViewHolder添加對應的InfoRecord

完成這個以后,RecyclerView對于要處理哪些動畫就了如指掌了,這個也是預布局的意義。

2. 真實布局

同樣不講具體的代碼,第二次布局完成以后,界面變成了:

第二次布局

看到上面的圖,你可能會有這樣的疑問,為什么要刪除的子視圖沒了?說好不講代碼的,打臉,只能甩出一段緩存Recycler的源碼了??:

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, Boolean dryRun) {
    final int scrapCount = mAttachedScrap.size();
    // Try first for an exact, non-invalid match from scrap.
    for (int i = 0; i < scrapCount; i++) {
        final ViewHolder holder = mAttachedScrap.get(i);
        if (... && (mState.mInPreLayout || !holder.isRemoved())) {
            // 在第一級緩存mAttachedScrap中,如果是刪除的ViewHolder
            // 預布局是可以使用的,真實布局不可以使用
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }
    //...
    return null;
}

雖然解決了當前的疑問,你可能還會有另外一個疑問,沒有了被刪除的子視圖,刪除動畫還怎么執行呢?我們還是先看看接下來的過程吧。

3. 執行動畫

之前我們記錄了那么多ViewHolder中子視圖的信息,現在到了使用的時候了:

private void dispatchLayoutStep3() {
    // ...
    if (mState.mRunSimpleAnimations) {
        // Step 3: Find out where things are now, and process change animations.
        // 找到當前的ViewHolder,執行需要執行的動畫
        for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
            ViewHolder holder = getChildViewHolderint(mChildHelper.getChildAt(i));
            long key = getChangedHolderKey(holder);
            final ItemHolderInfo animationInfo = mItemAnimator
                                    .recordPostLayoutInformation(mState, holder);
            ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
            if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                // ...
                if (oldDisappearing && oldChangeViewHolder == holder) {
                    // run disappear animation instead of change
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                } else {
                    // ...
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                    ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
                    // ...
                }
            } else {
                mViewInfoStore.addToPostLayout(holder, animationInfo);
            }
        }
        // Step 4: Process view info lists and trigger animations
        // 執行動畫
        mViewInfoStore.process(mViewInfoProcessCallback);
    }
    // 重置一些跟動畫有關的類
    // ...
    mState.mRunSimpleAnimations = false;
    mState.mRunPredictiveAnimations = false;
    // ...
    mViewInfoStore.clear();
}

這個函數的上半部分主要的目的是為了給ViewInfoStore里的ViewHolder相關的InfoRecord添加Post標簽,下半部分mViewInfoStore.process(mViewInfoProcessCallback)則是我們的核心功能 - 動畫執行,我們重點看一下這個方法:

void process(ProcessCallback callback) {
    for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
        final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
        final InfoRecord record = mLayoutHolderMap.removeAt(index);
        // 根據不同的Flag執行不同的動畫
        if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
            callback.unused(viewHolder);
        } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
            // Set as "disappeared" by the LayoutManager (addDisappearingView)
            if (record.preInfo == null) {
                callback.unused(viewHolder);
            } else {
                callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
            }
        } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
            // Appeared in the layout but not in the adapter (e.g. entered the viewport)
            callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
            // Persistent in both passes. Animate persistence
            callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_PRE) != 0) {
            // Was in pre-layout, never been added to post layout
            callback.processDisappeared(viewHolder, record.preInfo, null);
        } else if ((record.flags & FLAG_POST) != 0) {
            // Was not in pre-layout, been added to post layout
            callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_APPEAR) != 0) {
            // Scrap view. RecyclerView will handle removing/recycling this.
        } else if (DEBUG) {
            throw new IllegalStateException("record without any reasonable flag combination:/");
        }
        InfoRecord.recycle(record);
    }
}

// 回調接口
interface ProcessCallback {
    void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                    @Nullable ItemHolderInfo postInfo);
    void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
                    ItemHolderInfo postInfo);
    void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                    @NonNull ItemHolderInfo postInfo);
    void unused(ViewHolder holder);
}

ViewInfoStore#process這個關鍵方法中,遍歷mLayoutHolderMap獲取ViewHolder綁定的InfoRecord,根據不同flagInfoRecord,回調不同的方法,進而處理不同的動畫,回調接口的實現在RecyclerView中:

/**
 * The callback to convert view info diffs into animations.
 */
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
            new ViewInfoStore.ProcessCallback() {
    @Override
    public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
                            @Nullable ItemHolderInfo postInfo) {
        // 先移除緩存中的ViewHolder
        mRecycler.unscrapView(viewHolder);
        animateDisappearance(viewHolder, info, postInfo);
    }
    
    @Override
    public void processAppeared(ViewHolder viewHolder,
                            ItemHolderInfo preInfo, ItemHolderInfo info) {
        // 出現的動畫
        animateAppearance(viewHolder, preInfo, info);
    }
  
    @Override
    public void processPersistent(ViewHolder viewHolder,
                            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        viewHolder.setIsRecyclable(false);
        if (mDataSetHasChangedAfterLayout) {
            // since it was rebound, use change instead as we'll be mapping them from
            // stable ids. If stable ids were false, we would not be running any
            // animations
            if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
                                            postInfo)) {
                postAnimationRunner();
            }
        } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
            postAnimationRunner();
        }
    }
  
    @Override
    public void unused(ViewHolder viewHolder) {
        mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
    }
};

如果你注意到刪除方法,你的疑問就更大了,刪除的子視圖都沒了,還執行毛線刪除動畫?那我就得告訴你了:雖然當前RecyclerView沒有需要刪除的子視圖,但是當前的ViewInfoStoreViewHolder啊,所以在執行刪除動畫前會將ViewHolder中的子視圖重新添加到RecyclerView里面,這里看一下上面的processDisappeared方法調用的RecyclerView#animateDisappearance方法,來看看是不是這樣的:

void animateDisappearance(@NonNull ViewHolder holder,
            @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
    // 將子視圖重新加入到界面
    addAnimatingView(holder);
    holder.setIsRecyclable(false);
    // mItemAnimator執行刪除動畫
    if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
        postAnimationRunner();
    }
}

/**
 * 將刪除的子視圖重新添加進界面
 */
private void addAnimatingView(ViewHolder viewHolder) {
    final View view = viewHolder.itemView;
    final Boolean alreadyParented = view.getParent() == this;
    mRecycler.unscrapView(getChildViewHolder(view));
    if (viewHolder.isTmpDetached()) {
        // 重新attach回界面
        mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
    } else if (!alreadyParented) {
        // 添加視圖
        mChildHelper.addView(view, true);
    } else {
        mChildHelper.hide(view);
    }
}

一圖了解當前界面ViewHolder的狀態和需要執行的動畫:

執行動畫類型

如上面代碼塊中所看到的,刪除子視圖的動畫實際的執行者是mItemAnimator,其他動畫也是如此。

4. DefaultItemAnimator機制

ItemAnimator是一個抽象類,所以一些方法需要具體的類實現,在沒有指定具體的ItemAnimator情況下,系統使用了默認的DefaultItemAnimator。一圖簡單了解DefaultItemAnimator機制:

DefaultItemAnimator

除了DefaultItemAnimator,你還可以自定義一個ItemAnimator,主要實現增、刪、更新和移動等一些方法,本文就不再深入了,感興趣的同學可以自行研究。

DefaultItemAnimator的刪除動畫中,會對被刪除的子視圖執行透明度1-0的動畫,動畫結束后,會刪除子視圖和回收ViewHolder,位移動畫沒有放在透明度動畫結束后調用,而是使用時間為透明度動畫執行時間的延遲,所以看上去就像子視圖被刪除后下面的子視圖才開始網上位移的。

動畫執行完畢以后,圖片就變成了:


刪除完成

以上就是RecyclerView刪除部分的AdapterItemAnimator的調用原理,其他方法同學們可以自行分析~

三、總結

總結

還是那句話,沒有一遍調試解決不了源碼閱讀,如果有,那就是兩遍??~,抽絲剝繭RecyclerView源碼分析到此就結束了。

如果你想繼續了解RecyclcerView:

第一篇:《抽絲剝繭RecyclerView - 化整為零》
第二篇:《抽絲剝繭RecyclerView - LayoutManager》

特別分享篇:

《這么用GridLayoutManager,你可能還真沒嘗試過》

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容