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源码分析二之绘制与复用

参考链接:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值