抽絲剝繭RecyclerView - ItemAnimator & Adapter

前言

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

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

  • RecyclerView(總體結構)
  • Recycler
  • LayoutManger
  • ItemDecoration(略微瞭解)

縱觀RecyclerView,彷佛還剩下ItemAnimatorAdapter,那麼本文做爲**抽絲剝繭RecyclerView**系列的最後一篇,天然要將剩下的部分所有分析完畢(文末有往期文章的連接)。app

目錄

目錄

1、RecyclerView中的魔法師 - Adapter

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

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數據集發生了變化。此次咱們以刪除爲例來分析源碼。post

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);
	}
	// ...
}
複製代碼

該代碼主要做用有兩點:ui

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

數據變化通知對象是這個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進行界面刷新。

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,咱們在下面的動畫中分析~

2、界面交互的粘合劑 - 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);
				
			}
		}
![](https://user-gold-cdn.xitu.io/2019/9/16/16d3a4d52bfc6558?w=1500&h=750&f=png&s=27308)
		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) {
			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的調用原理,其餘方法同窗們能夠自行分析~

3、總結

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

若是你想繼續瞭解RecyclcerView:

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

特別分享篇:

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

相關文章
相關標籤/搜索