RecyclerView源碼分析三之動畫分析

介紹

在上一篇中,我們分析了RecyclerView的繪製與複用。接下來我們繼續分析RecyclerView的動畫實現原理。上圖展示的是一個Recyclerview的中,某個Item的刪除,從動畫執行preLayout階段-> postLayout階段 ->動畫執行-> 動畫結束後,item的回收 的整個流程。
在這裏插入圖片描述

1. notifyItemXXX的作用

在RecyclerView的,我們通過Adapter實現數據與View的綁定。當數據更改時,通過notifyItemXXX實現數據的更新。接下我們就首先分析一下notifyItemXXX究竟做了一件什麼事情(以notifyItemRemoved爲例)。

  /**
  * RecyclerView$Adapter類
  */
  public abstract static class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
		public final void notifyItemRemoved(int position) {
		  	//觀察者模式,將item移除事件通知類 “觀察者”
		  	//其中RecyclerViewDataObserver就是“觀察者之一”
		    mObservable.notifyItemRangeRemoved(position, 1);
		}
  }
  /**
  * RecyclerView內置的觀察者
  */
  private class RecyclerViewDataObserver extends AdapterDataObserver {
	//
     @Override
     public void onItemRangeRemoved(int positionStart, int itemCount) {
     	//通過mAdapterHelper,記錄“刪除操作”
         if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
         	//調用成員方法
             triggerUpdateProcessor();
         }
     }
     
     //該方法中,請求requestLayout進行重新繪製
     void triggerUpdateProcessor() {
			...
         mAdapterUpdateDuringMeasure = true;
         requestLayout();
     }
 }
 
 /**
 * AdapterHelper.java
 * 看一下AdapterHelper是如何記錄 “刪除操作”
 */
boolean onItemRangeRemoved(int positionStart, int itemCount) {
    if (itemCount < 1) {
        return false;
    }
    //將“刪除操作”封裝成對象,並保持到mPendingUpdates中
    //在重新繪製的,PreLayout階段進行使用。實現狀態的同步。
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
    mExistingUpdateTypes |= UpdateOp.REMOVE;
    return mPendingUpdates.size() == 1;
}

2. RecyclerView的兩個佈局階段

爲什麼RecylerView的動畫要分爲兩個階段

衆所衆知,ViewGroup可以通過LayoutTransition實現其內部childView的動畫變化。在普通的ViewGroup,其內部的View的動畫就是顯示和隱藏。但是ReyclerView需要的動畫有些不同,因爲它有滾動的效果。比如:如下圖所示,我們要刪除Item C,Item G進入屏幕,會給人一種誤解–在RecyclerView的尾部新增了Item G。
LayoutTransition
我們期望的效果是:
在這裏插入圖片描述
要想實現上圖中的預期效果,我們需要記錄屏幕中被影響變化的Item的 前後的位置變化和數據變化。而 PreLayout 和 PostLayout就是用來分別做這兩件事情的。

PreLayout:

在該階段,RecyclerView要求LayoutManager使用附加信息佈局之前的狀態。比如:它會請求重新繪製items(此時C已經被刪除了),LayoutManager運行它往常的佈局步驟,但知道’C’將被刪除,它佈局items來填充’C’留下的空間。這個設計最酷的地方是,RecyclerView仍然表現得好像“C”仍然在Adapter中一樣。

For example, when LayoutManager asks for the View for position 2, RecyclerView returns ‘C’ (getViewForPosition(2) == View(‘C’)) and if LayoutManager asks for position 4, RecyclerView returns the View for ‘E’ (although ‘D’ is the 4th item in the Adapter).

返回ItemView的LayoutParams有一個isItemRemove方法,LayoutManager可以使用該方法檢查這是否是一個要刪除的item。

PostLayout

‘C’ is not in the Adapter anymore. getViewForPosition(2) will return ‘D’ and getViewForPosition(4) will return ‘F’.

請記住,“C”的後臺項已經從適配器中刪除了,但是由於RecyclerView有它的視圖表示,它可以表現得好像“C”還在那裏一樣。換句話說,RecyclerView爲LayoutManager記賬。

2.1 PreLayout分析
private void dispatchLayoutStep1() {
	...
	if (mState.mRunSimpleAnimations) {
	    // Step 0: 找出沒有被刪除的Item
	    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;
	        }
	        //獲取當前item的位置信息
	        final ItemAnimator.ItemHolderInfo animationInfo = mItemAnimator
	                .recordPreLayoutInformation(mState, holder,
	                        ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
	                        holder.getUnmodifiedPayloads());
	        //將item的位置信息放入到mViewInfoStore中進行緩存
	        mViewInfoStore.addToPreLayout(holder, animationInfo);
	    }
	}
	
	//processAdapterUpdatesAndSetAnimationFlags方法中設置的標記
	if (mState.mRunPredictiveAnimations) {
	    // 保存原有位置的信息
	    // Save old positions so that LayoutManager can run its mapping logic、
	    saveOldPositions();
	    final boolean didStructureChange = mState.mStructureChanged;
	    mState.mStructureChanged = false;
	    //進行預繪製,將ViewHolder中的狀態信息,同步到ViewInfoStroe中
	    //便於在PostLayout中,執行動畫
	    mLayout.onLayoutChildren(mRecycler, mState);
	    mState.mStructureChanged = didStructureChange;
		...
	    clearOldPositions();
	} else {
	    //擦除位置信息
	    clearOldPositions();
	}
	...
}
2.2 PostLayout分析
private void dispatchLayoutStep3() {
   //進入動畫繪製階段
   mState.assertLayoutStep(State.STEP_ANIMATIONS);
   startInterceptRequestLayout();
   onEnterLayoutOrScroll();
   mState.mLayoutStep = State.STEP_START;
   if (mState.mRunSimpleAnimations) {
       for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
           ...
           //回去佈局後的,holder的當前位置信息
           final ItemAnimator.ItemHolderInfo animationInfo = mItemAnimator
                   .recordPostLayoutInformation(mState, holder);
           ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
           if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
               	   ...
               	   //將 postLayout階段的 animationInfo 緩存到mViewInfoStore中
                   mViewInfoStore.addToPostLayout(holder, animationInfo);
                  
               }else{
               		...
 				   //將 postLayout階段的 animationInfo 緩存到mViewInfoStore中
                   mViewInfoStore.addToPostLayout(holder, animationInfo);
				}
           } else {
           		//將 postLayout階段的 animationInfo 緩存到mViewInfoStore中
               mViewInfoStore.addToPostLayout(holder, animationInfo);
           }
       }

       //開始動畫的執行
       mViewInfoStore.process(mViewInfoProcessCallback);
   }
	...
}

3. 動畫的執行

接下來,我們接着上一節,繼續分析動畫。

// ViewInfoStore.java 
void process(ProcessCallback callback) {
   for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
   	  
       final InfoRecord record = mLayoutHolderMap.removeAt(index);
       if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
           callback.unused(viewHolder);
       } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
       	   ...
       	   //根據record.flags的不同狀態,執行不同的動畫
       	   //record.flags狀態的設置,源於 preLayout階段的,LayoutManager.onLayoutChildren()方法
           callback.processDisappeared(viewHolder, record.preInfo, record.postInfo); 
       } else{
       		...
       }
       //回收record info
       InfoRecord.recycle(record);
   }
}

接下來,看一下callback是怎麼具體處理動畫的。

/**
* 該成員變量 隸屬於 RecyclerView
*/
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
    @Override
    public void processDisappeared(ViewHolder viewHolder, @NonNull ItemAnimator.ItemHolderInfo info,
            @Nullable ItemAnimator.ItemHolderInfo postInfo) {
        //將viewHolder從一級緩存中去除
        mRecycler.unscrapView(viewHolder);
        //調用執行動畫的邏輯
        animateDisappearance(viewHolder, info, postInfo);
    }
};

 void animateDisappearance(@NonNull ViewHolder holder,
                              @NonNull ItemAnimator.ItemHolderInfo preLayoutInfo, @Nullable ItemAnimator.ItemHolderInfo postLayoutInfo) {
   //確保holder對應的View,已經被add到RecyclerView之上
   //這是執行動畫的基礎之一。如果是removed的holder,則需要進行hide操作                           
   addAnimatingView(holder);
    holder.setIsRecyclable(false);
    //到了這裏,mItemAnimator,是不是就不陌生了
    //這裏是需要執行的緩存動畫
    if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
    	//觸發動畫的執行
        postAnimationRunner();
    }
}

//注意,如果是“刪除操作”,在動畫執行完成的回掉裏面,需要調用方法
//dispatchRemoveFinished(holder);進行holder的dettach和recycle

/**
* RecyclerView的成員變量
*/
private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {

   ItemAnimatorRestoreListener() {
   }

   @Override
   public void onAnimationFinished(ViewHolder item) {
       item.setIsRecyclable(true);
       //如果item需要回收
       if (!item.shouldBeKeptAsChild()) {
           //進行回收
           if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
           	  //進行detach並且通知監聽器 
               removeDetachedView(item.itemView, false);
           }
       }
   }
}

上一篇RecyclerView源碼分析二之繪製與複用

參考鏈接:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章