大家有没有想过,scrap缓存存在的意义是什么?为什么存了后,又把它里面的viewholder又给删了?
在上一篇(RecyclerView源码走读)中我们分析过更新和删除表项的过程,下面来总结下scrap缓存的过程:
更新表项:页面上有表项0到9,总共有10个表项,然后更新表项0,在dispatchLayout1阶段(pre-layout),先把页面上的表项0到9存到scrap缓存中,由于表项0是更新的所以会把表项0放到changeScrap缓存中,把表项1到9存到attachScrap缓存中。同时会把这10个表项从页面上分离(child的parent置空,并把viewgroup中对应的child给置空),接着从scrap缓存中取出viewholder,由于存在更新表项,所以会去创建表项10的viewholder到recyclerview上。此时recyclerview中有11个表项了,同时把他们从scrap缓存中移除。
在dispatchLayout2阶段(post-layout)阶段,会把这11个表项加入到scrap缓存中,表项0还是放到changeScrap中,表项1到10加入到attachScrap缓存中。接着把他们从页面上分离。然后在fill中,从scrap缓存中获取viewholder,由于changeScrap不会在dispatchLayout2阶段生效,所以会创建了表项0,其余的表项正常从attachScrap缓存中获取。接着把所有的表项添加到recyclerview中,并把scrap中的viewholder缓存移除掉。但是表项10不会添加到recyclerview中,因为到表项9的时候,剩余空间就不够了,所以recyclerview中只有0到9的表项。注意:此时还有表项0个表项10分别存在于changeScrap和attachScrap中。
上面更新表项经历了两次的布局,分别是dispatchLayout1和dispatchLayout2,在这两个步骤中,先加入到scrap缓存中,然后再从scrap缓存中移除。从现象来看,dispatchLayout1中会生成11个表项,分别是0到10,然后在dispatchLayout2先移除所有的表项,然后添加0到9的表项,而在dispatchLayout2中表项0是重新生成的,因为它要实现更新。而调用两次的布局是为了实现动画而这么做,先生成11个表项,来把他们的位置都记录下来,然后第二次布局的时候把10个表项的位置也记录下来,然后最后根据存储的位置信息做动画。下面来分析下动画如何实现的:
在dispatchLayoutStep1将页面上可见的表项加入到ViewInfoStore中:
1
2
3
4
5
6
7
8
9
|
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.flags |= FLAG_PRE;
}
|
将viewholder和infoRecord绑定好关系,然后给该record添加上FLAG_PRE标记。注意:此处的InforRecord使用了对象池。
前面分析过在fill完之后,页面上会多出一个表项10,此时也会添加到ViewInfoStore中:
可以看到在onLayoutChildren之后会调用addToAppearedInPreLayoutHolders,此时只有表项10会调用addToAppearedInPreLayoutHolders:
1
2
3
4
5
6
7
8
9
|
void addToAppearedInPreLayoutHolders(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.flags |= FLAG_APPEAR;
record.preInfo = info;
}
|
所以只有表项10添加了FLAG_APPEAR标记。
在dispatchLayoutStep2走完后,页面上可见的表项就只剩0到9了,在dispatchLayoutStep3中将变化的viewholder调用animateChange,而其他的viewholder调用了addToPostLayout,在该例子中表项0会调用animateChange,表项1到9会调用addToPostLayout:
animateChange:当有viewholder发生变化的时候,会触发该方法,该方法会触发mItemAnimator的animateChange方法:
1
2
3
4
5
6
7
8
|
private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
boolean oldHolderDisappearing, boolean newHolderDisappearing) {
//省略代码
if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
postAnimationRunner();
}
}
|
调用了mItemAnimator的animateChange方法,并把改变之前的holder、itemholderinfo和改变之后的holder、itemholderinfo传进去了,默认的itemAnimator是DefaultItemAnimator,也是一个SimpleItemAnimator:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
if (DEBUG) {
Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView);
}
final int fromLeft = preInfo.left;
final int fromTop = preInfo.top;
final int toLeft, toTop;
if (newHolder.shouldIgnore()) {
toLeft = preInfo.left;
toTop = preInfo.top;
} else {
toLeft = postInfo.left;
toTop = postInfo.top;
}
return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop);
}
|
fromLeft、fromTop是老的holder的坐标,toLeft、toTop是新的holder的坐标,最后调用了另外一个animateChange:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
int fromX, int fromY, int toX, int toY) {
if (oldHolder == newHolder) {
// Don't know how to run change animations when the same view holder is re-used.
// run a move animation to handle position changes.
return animateMove(oldHolder, fromX, fromY, toX, toY);
}
final float prevTranslationX = oldHolder.itemView.getTranslationX();
final float prevTranslationY = oldHolder.itemView.getTranslationY();
final float prevAlpha = oldHolder.itemView.getAlpha();
resetAnimation(oldHolder);
int deltaX = (int) (toX - fromX - prevTranslationX);
int deltaY = (int) (toY - fromY - prevTranslationY);
// recover prev translation state after ending animation
oldHolder.itemView.setTranslationX(prevTranslationX);
oldHolder.itemView.setTranslationY(prevTranslationY);
oldHolder.itemView.setAlpha(prevAlpha);
if (newHolder != null) {
// carry over translation values
resetAnimation(newHolder);
newHolder.itemView.setTranslationX(-deltaX);
newHolder.itemView.setTranslationY(-deltaY);
newHolder.itemView.setAlpha(0);
}
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
return true;
}
|
如果新老holder是同一个,则调用animateMove,前面分析过oldHolder和newHolder不是同一个,所以先把newHolder的translationX和translationX设置到目标位置,其实此时的deltaX和deltaY为0,因为目标的viewholder位置也在原来表项0的位置,接着把透明度设置为0,最后创建了ChangeInfo,放入到mPendingChanges中。当animateChange返回true后,会调用postAnimationRunner方法:
1
2
3
4
5
6
|
void postAnimationRunner() {
if (!mPostedAnimatorRunner && mIsAttached) {
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
|
ViewCompat.postOnAnimation(this, mItemAnimatorRunner)是给Choreographer中添加一条CALLBACK_ANIMATION类型的事件,等到下一个vsync信号来的时候,就会执行mItemAnimatorRunner:
1
2
3
4
5
6
7
8
9
|
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
if (mItemAnimator != null) {
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
};
|
最终会执行DefaultItemAnimator的runPendingAnimations方法,该方法会执行changesPending的逻辑,最终会执行animateChangeImpl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
void animateChangeImpl(final ChangeInfo changeInfo) {
final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
final View view = holder == null ? null : holder.itemView;
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
if (view != null) {//改变之前的view
final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
getChangeDuration());
mChangeAnimations.add(changeInfo.oldHolder);
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchChangeStarting(changeInfo.oldHolder, true);
}
@Override
public void onAnimationEnd(Animator animator) {
oldViewAnim.setListener(null);
view.setAlpha(1);
view.setTranslationX(0);
view.setTranslationY(0);
dispatchChangeFinished(changeInfo.oldHolder, true);
mChangeAnimations.remove(changeInfo.oldHolder);
dispatchFinishedWhenDone();
}
}).start();
}
if (newView != null) {//改变之后的view
final ViewPropertyAnimator newViewAnimation = newView.animate();
mChangeAnimations.add(changeInfo.newHolder);
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
.alpha(1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchChangeStarting(changeInfo.newHolder, false);
}
@Override
public void onAnimationEnd(Animator animator) {
newViewAnimation.setListener(null);
newView.setAlpha(1);
newView.setTranslationX(0);
newView.setTranslationY(0);
dispatchChangeFinished(changeInfo.newHolder, false);
mChangeAnimations.remove(changeInfo.newHolder);
dispatchFinishedWhenDone();
}
}).start();
}
}
|
该方法里面通过view的animate方法给view做动画,改变之前的view位置不会发生变化,只会发生alpha的动画,而改变之后的view会做从0到1的透明度动画。所以最终页面上会出现闪的一下,这个是做更新操作。
上面分析的是表项0会调用animateChange,表项1到9会执行addToPostLayout:
1
2
3
4
5
6
7
8
9
|
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;
}
|
在dispatchLayout1的时候,表项1到9会添加上FLAG_PRE标记,接着在此处又添加上FLAG_POST标记,表项10添加了FLAG_APPEAR标记。所有的标记添加完后,最终会执行ViewInfoStore的process:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
final InfoRecord record = mLayoutHolderMap.removeAt(index);
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_APPEAR) != 0) {
// Scrap view. RecyclerView will handle removing/recycling this.
}
InfoRecord.recycle(record);
}
}
|
这里省略了无关紧要的代码,表项1到9会调用callback.processPersistent回调,表项10不做相关更新:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Override
public void processPersistent(ViewHolder viewHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
viewHolder.setIsRecyclable(false);
if (mDataSetHasChangedAfterLayout) {
// since it was rebound, use change instead as we'll be mapping them from
// stable ids. If stable ids were false, we would not be running any
// animations
if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
postInfo)) {
postAnimationRunner();
}
} else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
postAnimationRunner();
}
}
|
由于mDataSetHasChangedAfterLayout是在setAdapter中设置的,所以会走mItemAnimator.animateChange逻辑,在上面分析过,如果两个viewholder是同一个,则调用animateMove方法,而位置信息又不发生变化,所以animateChange返回false,因此也不会触发postAnimationRunner。
recyclerview中只要被添加到页面上的viewholder,并且是scrap缓存中的,最终都会从scrap缓存中移除。而在上面的例子中,分析dispatchLayout2的时候总共有11个表项,分别是原来的表项0(存在于changeScrap缓存中),dispatchLayout1创建的表项10(存在于attachScrap中)。在dispatchLayout3做完动画后,会清空掉scrap缓存,也就是调用了mLayout.removeAndRecycleScrapInt方法:
1
2
3
4
5
|
void removeAndRecycleScrapInt(Recycler recycler) {
//省略代码
recycler.clearScrap();
//省略代码
}
|
最终会调用clearScrap来清空scrap缓存。
关于删除表项的动画处理,可以看这里:RecyclerView 动画原理 | 如何存储并应用动画属性值?