转载自琼珶和予
RecyclerView 源码分析(四)RecyclerView的动画机制
RecyclerView的动画机制
本文不会分析ItemAnimator相关的知识,而是理解RecyclerView怎么执行ItemAnimator的,有关ItemAniamtor的知识,后面我会写专门的文章来分析。
本文参考资料:
RecyclerView animations - AndroidDevSummit write-up
RecyclerView.ItemAnimator终极解读(一)–RecyclerView源码解析
注意,本文所有的代码都来自于27.1.1。
1. 概述
RecyclerView之所以受欢迎,有一部分的原因得归功于它的动画机制。我们可以通过RecyclerView的setItemAnimator方法来给每个Item设置在不同行为下,执行不同的动画
,非常的简单。尽管我们知道怎么给RecyclerView设置动画,但是RecyclerView是怎么通过ItemAnimator来给每个Item实现动画,这里面的原理值得我们去研究和学习。
在正式分析RecyclerView的动画机制之前,我们先对几个词语有一个概念,我们来看看:
词语 | 含义 |
---|---|
Disappearance | 表示在动画之前,ItemView是可见的,动画之后就可不见了。这里的操作包括,remove操作和普通的滑动导致ItemView划出屏幕 |
Appearance | 表示动画之前,ItemView是不可见,动画之后就可见了。这里的操作包括,add操作和普通的滑动导致ItemView划入屏幕 |
Persistence | 表示动画前后,状态是不变的。这里面的操作包括,无任何操作 |
change | 表示动画前后,状态是不变的。这里面的操作包括,change操作 。 |
还有注意的一点就是,ViewHolder不是用来记录ItemView的位置信息,而是进行数据绑定的,所以在动画中,关于位置信息的记录不是依靠ViewHolder来实现的,而是依靠一个叫ItemHolderInfo的类实现的,在这个类里面,有四个成员变量,分别记录ItemView的left、top、right和bottom四个位置信息。
最后还需要注意一点就是,我们从RecyclerView的三大流程中可以得到,在RecyclerView的内部,dispatchLayout分为三步,其中dispathchLayoutStep1被称为预布局,在这里主要是保存ItemView的OldViewHolder,同时还会记录下每个ItemView在动画之前的位置信息;与之对应的dispathchLayoutStep3被称为后布局,主要结合真正布局和预布局的相关信息来实现进行动画,当然前提是RecyclerView本身支持动画。
本文打算从两个角度来分析RecyclerView的动画,一是从普通三大的流程来看,这是动画机制的核心所在;二是从Adapeter的角度上来看,看看我们每次在调用Adapter的notify相关方法之后,是怎么进行执行动画的
(实际上也是回到三大流程里面)。
1. 再来看RecyclerView的三大流程
取这个题目,我感觉有特别的含义。首先,本次分析动画机制就是重新来看看三大流程,当然本次分三大流程肯定没有之前的那么仔细,其次侧重点也不同;其次,本次再来看RecyclerView的三大流程,还可以填之前在分析RecyclerView的三大流程留下的坑。
本次的分析重点在于dispathchLayoutStep1和dispathchLayoutStep3,不会分析完整的三大流程,所以,还有不懂RecyclerView三大流程的同学,可以参考我的文章:RecyclerView 源码分析(一) - RecyclerView的三大流程。
我们先来看看dispatchLayoutStep1方法:
private void dispatchLayoutStep1() {
// ······
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
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;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
// This is NOT the only place where a ViewHolder is added to old change holders
// list. There is another case where:
// * A VH is currently hidden but not deleted
// * The hidden item is changed in the adapter
// * Layout manager decides to layout the item in the pre-Layout pass (step1)
// When this case is detected, RV will un-hide that view and add to the old
// change holders list.
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (viewHolder.shouldIgnore()) {
continue;
}
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);