先来思考两个问题~
-
为什么改变数据后调用notifyDataSetChanged不发生删除动画?调用notifyItemRemoved等notify这样的操作会发生动画? -
两次layout是为了什么?
删除动画分析
我们知道RecyclerView的动画执行和它的layout过程息息相关,研究动画的执行实际上也是在研究RecyclerView的布局layout过程~
先找到删除动画的触发入口(我们以DefaultAnimator为例来看动画的执行)
1.1 notifyItemRemoved开始
最后会调用到 triggerUpdateProcessor
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
//布局触发动画,动画过程都发生在布局计算的时候
requestLayout();
}
}
1.2 触发了绘制流程,去看onLayout过程
RecyclerView的布局分成了三个步骤。dispatchLayoutStep1 , dispatchLayoutStep2 , diapatchLayoutStep3
我们依次来分析这三个阶段都干了些什么不可告人的秘密~
1.3 dispatchLayoutStep1 这个方法只保留重点的地方来理解
private void dispatchLayoutStep1() {
// 1. 第一步 给ViewHolder设置flag
processAdapterUpdatesAndSetAnimationFlags();
... ...
if (mState.mRunSimpleAnimations) {
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
//2. 第二步 记录每个ViewHolder对象,它们即将进行preLayout
mViewInfoStore.addToPreLayout(holder, animationInfo);
... ... ...
if (mState.mRunPredictiveAnimations) {
... ... ...
//3. 第三步 进行 preLayout
mLayout.onLayoutChildren(mRecycler, mState);
... ... ...
1.3.1 第一步 processAdapterUpdatesAndSetAnimationFlags()
这个方法流程稍为简化一下,关键看remove的动画过程:
private void processAdapterUpdatesAndSetAnimationFlags() {
... ... ...
if (predictiveItemAnimationsEnabled()) {
//核心步骤
mAdapterHelper.preProcess();
}
... ... ...
mAdapterHelper.preProcess
void preProcess() {
mOpReorderer.reorderOps(mPendingUpdates);
final int count = mPendingUpdates.size();
for (int i = 0; i < count; i++) {
UpdateOp op = mPendingUpdates.get(i);
switch (op.cmd) {
case UpdateOp.ADD:
applyAdd(op);
break;
case UpdateOp.REMOVE:
applyRemove(op);
break;
case UpdateOp.UPDATE:
applyUpdate(op);
break;
case UpdateOp.MOVE:
applyMove(op);
break;
}
if (mOnItemProcessedCallback != null) {
mOnItemProcessedCallback.run();
}
}
mPendingUpdates.clear();
}
这里可以留一个小结论:
这个方法会消耗完所有的mPengindUpdates,这样就可以方便后面计算出所有ViewHolder的mPreLayoutPosition mPosition mOldPosition
继续跟到RecyclerView offsetPositionRecordsForRemove证实上面的所说
//positionStart表示删除的起始地方 itemCount表示个数
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) {
//mPosition mPreLayoutPosition mOldPosition
// 位置改变的holder 重新计算位置
holder.offsetPosition(-itemCount, applyToPreLayout);
mState.mStructureChanged = true;
} else if (holder.mPosition >= positionStart) {
// 给要删除的ViewHolder 添加一个Flag FLAG_REMOVE
holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount,
applyToPreLayout);
mState.mStructureChanged = true;
}
}
}
mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
requestLayout();
}
对于位置需要改变的ViewHolder们, offsetPosition方法重新计算了 mOldPosition mPreLayoutPosition mPosition
void offsetPosition(int offset, boolean applyToPreLayout) {
if (mOldPosition == NO_POSITION) {
mOldPosition = mPosition;
}
if (mPreLayoutPosition == NO_POSITION) {
mPreLayoutPosition = mPosition;
}
if (applyToPreLayout) {
mPreLayoutPosition += offset;
}
mPosition += offset;
... ...
小结论
针对每次update(指的是mPendingUpdates里的元素),都会遍历所有的可能会被影响到的ViewHolder,重新计算preLayoutPostion mPostion(可以理解为postLayoutPosition)。 对于某些要被删除的ViewHolder,走flagRemovedAndOffsetPosition方法。所以,最后消耗完mPendingUpdates之后,我们就安排明白了所有ViewHolder的preLayoutPosition和postLayoutPosition
1.3.2 第二步 mViewInfoStore.addToPreLayout 所有的ViewHolder挨个记录
void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.preInfo = info; //record.preInfo
record.flags |= FLAG_PRE; // 设置flag
}
1.3.3 第三步 dispatchLayoutStep1 -> mLayout.onLayoutChildren
这是一次布局过程,我们称为preLayout,我理解它的目的是记录每个ViewHolder在未变化之前的位置信息,方便执行比如move动画时来确定动画的起始位置坐标,onLayoutChildren布局逻辑涉及到比如LinearLayoutManager的布局逻辑,ViewHolder复用逻辑等(不详细展开)
1.5 继续往下看dispatchLayoutStep2
可以看到disptchLayout2的代码明显要简洁了很多,这里也执行了一次布局,这次布局的不同之处在于,使用的是ViewHolder的getLayoutPosition返回的是mPosition。而preLayout的getLayoutPosition返回的是mPreLayoutPosition,所以这次layout是根据实际数据进行的一次layout
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
... ...
//postLayout
mLayout.onLayoutChildren(mRecycler, mState);
... ...
stopInterceptRequestLayout(false);
}
1.6 dispatchLayoutStep3 分析
需要注意两个地方,一个是会通过方法addToPostLayout把当前的ViewHolder当前的flags添加上 FLAG_POST
第二步是process真正触发动画的执行!
private void dispatchLayoutStep3() {
... ... ...
if (mState.mRunSimpleAnimations) {
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
//更新动画走这里
... ... ...
} else {
//1. 删除动画 走这里
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
//2.触发动画的执行
mViewInfoStore.process(mViewInfoProcessCallback);
}
}
1.6.1 addToPostLayout
void addToPostLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.postInfo = info;
record.flags |= FLAG_POST;
}
这里会有一个有意思的事情,我们知道在preLayout我们已经在mLayoutHolderMap放入了所有参与preLayout的ViewHolder信息,并且添加了FLAG_PRE。在这里我们又在mLayoutHolderMap添加了动画完毕之后的ViewHolder,并加上了FLAG_POST,这里就有几种情况:
- 只带有FLAG_PRE
- 只带有FLAG_POST
- 既带有FLAG_PRE,也有FLAG_POST
作用是什么呢? 带有FLAG_PRE说明参与了PRE_LAYOUT,如果没有带FLAG_POST,说明这个ViewHolder被删除了,要做删除动画。如果只带有FLAG_POST,但是没有带FLAG_PRE,说明是新增的,要做新增动画,如果两个FLAG都带,说明可能发生了移动动画,或者就是没动
1.6.2 删除动画执行过程
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
... ...
} else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) { //move动画走这里
// 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) { //add动画
// Was not in pre-layout, been added to post layout
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
... ...
}
}
继续跟删除动画 mViewInfoProcessCallback processDisappeared
public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
@Nullable ItemHolderInfo postInfo) {
mRecycler.unscrapView(viewHolder);
animateDisappearance(viewHolder, info, postInfo);
}
animateDisappearance
void animateDisappearance(@NonNull ViewHolder holder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
... ...
// animateDisappearance
if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
SimpleItemAnimator animateDisappearance
@Override
public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {、
... ...
if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
... ...
} else {
return animateRemove(viewHolder);
}
}
DefaultItemAnimator animateRemove
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
//添加到 mPendingRemovals 等待触发
mPendingRemovals.add(holder);
return true;
}
DefaultItemAnimator runPendingAnimations
public void runPendingAnimations() {
//remove 动画的ArrayList
boolean removalsPending = !mPendingRemovals.isEmpty();
//move 动画的ArrayList
boolean movesPending = !mPendingMoves.isEmpty();
//change 动画的ArrayList
boolean changesPending = !mPendingChanges.isEmpty();
//add 动画ArrayList
boolean additionsPending = !mPendingAdditions.isEmpty();
... ...
// First, remove stuff 优先删除动画
for (RecyclerView.ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder);
}
mPendingRemovals.clear();
// Next, move stuff move动画
... ...
for (MoveInfo moveInfo : moves) {
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
moveInfo.toX, moveInfo.toY);
}
... ...
}
// Next, change stuff, to run in parallel with move animations 更新动画
... ...
for (ChangeInfo change : changes) {
animateChangeImpl(change);
}
... ...
}
// Next, add stuff 添加动画
... ...
for (RecyclerView.ViewHolder holder : additions) {
animateAddImpl(holder);
}
...
}
}
animateRemoveImpl 最后用属性动画实现删除动画
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
... ...
animation.setDuration(getRemoveDuration()).alpha(0).setListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
... ...
}
@Override
public void onAnimationEnd(Animator animator) {
... ...
}
}).start();
}
总结
-
理解一下preLayout和postLayout.在RecyclerView执行动画时,会对RecyclerView执行两次layout操作.第一次我们称之为preLayout,计算的是数据未改变时的各个子View的状态.第二次称之为postLayout,这次布局使用的是变化了之后的数据,对RecyclerView进行一次重新layout.两次layout之后的目的是计算出所有的子View该做何种动画,动画的执行距离等.
-
删除动画主要是依靠layout过程来实现的,layout分两步来做,第一步preLayout,存储每个在RecyclerView上ViewHolder对象的信息,保存在record.preInfo中(mViewInfoStore.addToPreLayout);第二步,一般称为postLayout,进行动画完成后的布局计算,保存在record.postInfo里(mViewInfoStore.addToPostLayout),这两次绘制的信息也是动画计算的关键。
-
我们删除一个item的时候,执行这个动画的时候,有些ViewHolder是要被移除的,有的item是要被move的,有些是不要动的,我们如何判断呢?实际上是根据FLAG_PRE和FLAG_POST来判断,只有FLAG_PRE说明被删除了,只有FLAG_POST说明是新增的,两者都有可能是move动画。能被执行addToPreLayout的ItemInfo,都有FLAG_PRE,能被执行addToPostLayout的ItemInfo,都有FLAG_POST
-
关于preLayout和postLayout的意义,就是计算保存动画开始前和结束后的子view的位置信息,根据这些信息和设置的一些flag来计算执行何种动画,如果移动位置,可以用来计算移动多少距离。
-
notifyDataSetChanged只是单纯的改变数据集合,然后重新绘制RecyclerView,并没有涉及到preLayout和postLayout。
-
mChangedScrap用于更新动画中,记录的ViewHolder是需要重新绑定数据的。
-
mPendingUpdates在preLayout之前就已经被全部consume了。