天天看點

RecyclerView源碼分析三之動畫分析

介紹

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

RecyclerView源碼分析三之動畫分析

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。

RecyclerView源碼分析三之動畫分析

我們期望的效果是:

RecyclerView源碼分析三之動畫分析

要想實作上圖中的預期效果,我們需要記錄螢幕中被影響變化的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源碼分析二之繪制與複用

參考連結:

  • RecyclerView動畫原理分析一
  • RecyclerView動畫原理分析二

繼續閱讀