前两篇讲了RecyclerView的构造函数和setLayoutManager以及setAdapter方法,这篇就开始学习RecyclerView的真正显示过程
RecyclerView继承自ViewGroup,所以也遵守一般view的绘制过程,首先会调用onMeasure
方法,我们就先看onMeasure
方法
onMeasure的作用是决定自身和子view的尺寸,这点要事先清楚。
好,直接上源码,代码不短也不长,直接全部贴上了吧
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
/**
* This specific call should be considered deprecated and replaced with
* {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
* break existing third party code but all documentation directs developers to not
* override {@link LayoutManager#onMeasure(int, int)} when
* {@link LayoutManager#isAutoMeasureEnabled()} returns true.
*/
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
onExitLayoutOrScroll();
if (mState.mRunPredictiveAnimations) {
mState.mInPreLayout = true;
} else {
// consume remaining updates to provide a consistent state with the layout pass.
mAdapterHelper.consumeUpdatesInOnePass();
mState.mInPreLayout = false;
}
mAdapterUpdateDuringMeasure = false;
stopInterceptRequestLayout(false);
} else if (mState.mRunPredictiveAnimations) {
// If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
// this means there is already an onMeasure() call performed to handle the pending
// adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
// with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
// because getViewForPosition() will crash when LM uses a child to measure.
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
return;
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
startInterceptRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
stopInterceptRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
老规矩,一行一行来看。
最开始会判断mLayout是不是null,也就是有没有设置LayoutManager,如果没有就调用defaultOnMeasure(widthSpec, heightSpec)
,就是只对RecyclerView进行measure,不处理子view,然后就返回了。当然我们这里有设置LayoutManager(此文章系列都是用LinearLayoutManager),所以接下来要判断mLayout.isAutoMeasureEnabled()
返回值,这个isAutoMeasureEnabled
方法有必要讲一下,因为注释有一大堆,注释那么多,肯定很重要,这里就翻译一下注释,也算是知道了这个方法的作用。
翻译如下:
首先这个方法的返回值指定了RecyclerView在measure的过程中是使用AutoMeasure机制还是让LayoutManager的
onMeasure
去完成measure工作,返回true
表示使用AutoMeasure机制。默认情况下,LayoutManager会返回false
,但是,它的子类都是返回true
,包括LinearLayoutManager。当你确定要返回false
的时候,LayoutManager的子类需要重写onMeasure方法,因为RecyclerView的measure工作会全权交个LayoutManager的onMeasure
方法去做。如果返回true,则你实现的LayoutManager的子类不需要实现onMeasure
方法。
所以这个AutoMeasure机制又是个啥东西呢?文档是这样介绍的:
AutoMeasure使用一种简单且令人满意的方法去处理RecyclerView的layout过程,包括被LayoutManager封装的子view的布局过程,也就是通过LayoutManager的
onLayoutChildren
方法来实现子view的布局,然后通过子view的尺寸和位置来确定RecyclerView的尺寸。更详细的定义如下:
- 在RecyclerView的
onMeasure
方法中,如果提供的长宽的mode都是EXACTLY,RecyclerView就直接设置指定的值,然后返回。
- 如果长和宽两者中有任何一个不是EXACTLY,RecyclerView就开始layout过程(WTF!在onMeasure方法中就就进行layout?事实证明确实是这样搞的。。。)。首先会处理那些还没有被处理的Adapter操作,比如add,remove,然后决定是否要进行预布局(这个预布局是什么呢?就是为预动画(predictive animator)服务的,那这个预动画又是什么呢?就是让你看到item移除,添加,和改变的整个过程,详细可以参考google开发者大会视频,链接在这里RecyclerView Animations and Behind the Scenes (Android Dev Summit 2015))。如果决定要预布局,则首先会调用LayoutManager的
onLayoutChildren
方法,并将State类的isPreLayout
方法设置为true
,在这个阶段LayoutManager的getHeight
和getWidth
方法返回的仍然是上次layout时候RecyclerView的width和height。在处理完预操作之后,RecyclerView会调用onLayoutChildren
方法,并将State的isMeasuring
设置为true
,将isPreLayout
设置为false
。这时候,LayoutManager可以通过调用getHeight
,getHeightMode
,getWidth
,getWidthMode
来获取测量结果。- 在layout计算之后,RecyclerView通过计算child view的边界(加上了RecyclerView的padding)来设定自身的measured width和height。LayoutManager可以重写
setMeasuredDimension(Rect,int,int)
方法,来选择不同的值,举个例子,GridLayoutManager重写了这个方法,来处理如下的情况,用户指定了grid有3列,但是实际上却只有两个数据条目,在这种情况下,还是应该将宽度设置为3列而不是2列。- 接下来,任何对RecyclerView的
onMeasure
的调用都会运行onLayoutChildren
方法,并伴随着State的isMeasuring()
返回true
和isPreLayout()
返回false
。RecyclerView会为了动画而去管理好那些真正被增加,删除,移动,改变的view,这样LayoutManager就不需要担心他们,并且在处理每一个onLayoutChildren
的时候好像是在处理最后一个一样(一脸闷逼啊)- 当measure完成,且RecyclerView的
onLayout
方法被调用,RecyclerView会检查是否已经在measure过程中做了layout计算,如果做了,它会重用那些信息,如果发现上次的测量结果和最终的值有不一样,或者adapter数据在measure和onlayout过程中发生改变,它可能仍然会决定再跑一次onLayoutChildren
。最后就是跑动画。
好了,讲完了,如果有认真看的同学,肯定会一脸闷逼,暂时不管他,先看下去。
回到onMeasure()
方法,在这里mLayout.isAutoMeasureEnabled()
是返回true
,我们就看true
下面是怎么走。其实下面的代码都是按照上面的机制走的,有兴趣的同学可以自己一一对照。
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
如果长和宽都是EXACTLY,就直接返回,当然我们这里不是,所以继续往下走。
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
我们这里假设是第一次运行,mState.mLayoutStep的值就是初始值State.STEP_START,所以接下来要走dispatchLayoutStep1(),此君代码有点多,我就一部分一部分贴吧。。
mState.assertLayoutStep(State.STEP_START);
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
eatRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
....
做了以下事情
layoutStep
是不是State.STEP_START,当然是。fillRemainingScrollValues(mState)
就是如果当前正在scroll,则把没有滑完的距离记录到mState
中mState.mIsMeasuring
设置为false
,表示当前还没开始measure
eatRequestLayout()
,这个方法会给mEatRequestLayout
值加一,然后再设置mLayoutRequestEaten
的值,目前对这两个值的作用还不是很清楚,应该是会丢弃requestLayout
的请求,然后再某个时间再进行layout。mViewInfoStore
,这个变量是用来保存view动画有关的数据,尼玛名字能不能取的好一点onEnterLayoutOrScroll()
,会对变量mLayoutOrScrollCounter
加一,表示进行布局和scroll的次数,具体的作用还是要看下去才知道。processAdapterUpdatesAndSetAnimationFlags()
,这个方法中会消耗掉还未被处理的Adapter操作,然后决定跑什么类型的动画,贴一下代码 private void processAdapterUpdatesAndSetAnimationFlags() {
if (mDataSetHasChangedAfterLayout) {
// Processing these items have no value since data set changed unexpectedly.
// Instead, we just reset it.
mAdapterHelper.reset();
mLayout.onItemsChanged(this);
}
// simple animations are a subset of advanced animations (which will cause a
// pre-layout step)
// If layout supports predictive animations, pre-process to decide if we want to run them
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
做了如下事情:
mDataSetHasChangedAfterLayout
是否为true
,这个变量的意思是Adapter的数据有没有被整个更新过,这个值只有在setAdapter
,swapAdapter
,和notifyDataSetChanged
方法中会被设置为true
。回到代码,如果判断到mDataSetHasChangedAfterLayout
,RecyclerView就会清空未完成的Adapter操作,因为数据都被整个换过了,再做这些操作是没有意义的,并调用mLayout.onItemsChanged(this)
,所以如果想要在数据被整个换过的时候做一些动作,可以实现LayoutManager的onItemsChanged
方法。predictiveItemAnimationsEnabled()
,判断是否支持predictive item动画,通过阅读代码,LayoutManager本身是不支持的,但是它的子类LinearManager在大多数情况下是支持的,所以我们这里就把它的结果看做是true
,下面就要走mAdapterHelper.preProcess()
,这个方法会内部会先把没有处理过的item操作重新排序,主要是把move操作排到最后,因为move如果放在中间的话,操作起来比较麻烦。然后再逐个处理里面的操作,操作是以UpdateOp
来封装的,操作种类有ADD,REMOVE,MOVE,UPDATE
。preProcess()
方法对操作处理,会先把操作放到一个mPostponedList中,然后调用Callback的实现者去真正完成这些操作,而这个实现者就是RecyclerView,但是RecyclerView在处理这些操作的时候,并不会马上去更新ui,而是去更新viewhoder里面的position信息。好了dispatchLayoutStep1()
方法的第一部分已经讲完了。下面贴第二部分
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
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);
boolean wasHidden = viewHolder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (!wasHidden) {
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
resumeRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
mState.mTrackOldChangeHolders
,mState.mRunSimpleAnimations
,mItemsAddedOrRemoved
,mItemsChanged
, mState.mInPreLayout
都是false。下面的if(mState.mRunSimpleAnimations)
和if(mState.mRunPredictiveAnimations)
也都跑不进去。这两个if我会在后面讲LinearLayoutManager的时候再研究clearOldPositions
,将所有viewholder
的mOldPosition
和mPreLayoutPosition
复位为NO_POSITION
。resumeRequestLayout
方法,本来在这个方法里面是把之前吃掉的layout请求吐出来,也就是调用dispatchLayout()
进行真正的layout,但是由于传进去的参数是false
,也就是不进行layout,所以这里只是对mEatRequestLayout
和mLayoutRequestEaten
这两个变量进行操作。mState.mLayoutStep
设置为 State.STEP_LAYOUT
,表示接下来要进行真正的layout到这里dispatchLayoutStep1()
方法就结束了。
回到onMeasure()
方法
接下来会调用mLayout.setMeasureSpecs(widthSpec,heightSpec)
里面做的事情是分别判断width和height的mode是不是UNSPECIFIED
,如果是,就再看ALLOW_SIZE_IN_UNSPECIFIED_SPEC
是不是false,如果是false就将height和width设置为0,这个ALLOW_SIZE_IN_UNSPECIFIED_SPEC
只有在大于等于M的机器上才是true
。
然后将mState.mIsMeasuring
设施为true
,接着调用dispatchLayoutStep2()
方法,我们这里看下这个源码
/**
* The second layout step where we do the actual layout of the views for the final state.
* This step might be run multiple times if necessary (e.g. measure).
*/
private void dispatchLayoutStep2() {
eatRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
resumeRequestLayout(false);
}
从注释可以知道,这里是真正进行layout的地方,然后还可能跑多次。
我们从第4行开始,这里调用了mAdapterHelper.consumeUpdatesInOnePass()
,就是将mPendingUpdates
里面的操作消耗掉,这个方法最后会调用到LayoutManager的onItemsAdded,onItemsRemoved,onItemsUpdated,onItemsMoved
,但是LayoutManager和LinearLayoutManager都没有在这些方法里做什么事情,猜想应该这些方法跟真正的布局child没有关系。
接下里看这部分
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mInPreLayout
设为false
,然后调用mLayout.onLayoutChildren()
方法,这个方法是真正对child进行layout的地方,不过也不是只要跑这个方法就会真正对child进行布局,比如要进行predictive animator。关于onLayoutChildren
的源码这里也先不讲了,后面专门讲LinearLayoutManager的时候再讲。onMeasure
方法,在调用完dispatchLayoutStep2()
之后,会调用LayoutManager的setMeasuredDimensionFromChildre()
方法,这个方法会根据child的布局算出RecyclerView的边界,不过LayoutManager可以根据自己的需求做特殊处理,如前面提到的GridLayoutManager。shouldMeasureTwice()
方法来判断,默认是false
,而sdk中也只有LinearLayoutManager才有重写这个方法,返回true
的条件是RecyclerView的height和width没有一个mode是Exact
,且至少有一个孩子的height和width的mode也没有一个是Exact
。二次measure做的事情和前面的一样,就不再赘述。我这里只讲mLayout.mAutoMeasure
等于true
的情况,因为目前SDK自带的LayoutManager子类都是返回true,也就是说支持autoMeasure模式。RecyclerView的onMeasure方法就讲完了,按照view的渲染顺序,接下来调用onLayout方法。
onlayout
方法的作用是对childview进行布局,和onMeasure是差别的,因为onMeasure
还会对自身进行测量,而onLayout
只对child进行布局。
废话不多讲,直接上代码
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
看了代码,我只想说一句,so easy,妈妈再也不用担心我的学习。。。,想不到竟然只有四行代码,而且其中2行还是跟debug有关,真是太幸福了。好了直接看dispatchLayout()
方法,先看注释,经过翻译,意思如下
封装了 layoutChildren()对layout过程中引起的动画变化的处理,动画的工作基于如下假定:显示的item有五种类别,
- PERSISTENT: 在layout之前还是之后,那些依然可见的item
- REMOVED: layout之前可见,然后被app移除的items
- ADDED: layout之前不存在,后来被app添加的items
- DISAPPEARING: 在layout之前之后一直存在,但是在layout之后被移出屏幕的item,比如当前屏幕上有10个item,然后你在第五个位置添加了一个item,这样第10个item就会被挤到屏幕外,这样第10个item就是DISAPPEARING item。
- APPEARING: 在layout之前不可见,但在layout之后移到屏幕内的item
所有的操作都是为了确定在layout前后哪些item是可见的,哪些是不可见的,然后分别给每个item指定以上五种种类中的一种, 然后根据不同的种类,使用不同的方法来构建动画,
PERSISTENT通过RecyclerView.ItemAnimator.animatePersistence(RecyclerView.ViewHolder, RecyclerView.ItemAnimator.ItemHolderInfo, RecyclerView.ItemAnimator.ItemHolderInfo) 来构建
DISAPPEARING通过RecyclerView.ItemAnimator.animateDisappearance(RecyclerView.ViewHolder, RecyclerView.ItemAnimator.ItemHolderInfo, RecyclerView.ItemAnimator.ItemHolderInfo) 来构建
APPEARING通过RecyclerView.ItemAnimator.animateAppearance(RecyclerView.ViewHolder, RecyclerView.ItemAnimator.ItemHolderInfo, RecyclerView.ItemAnimator.ItemHolderInfo) 来构建
被改变的item通过RecyclerView.ItemAnimator.animateChange(RecyclerView.ViewHolder,RecyclerView.ViewHolder, RecyclerView.ItemAnimator.ItemHolderInfo, RecyclerView.ItemAnimator.ItemHolderInfo)来构建
好了注释讲完了,应该可以知道一点信息了。接下来看源码
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
操作如下:
mState.mIsMeasuring
设置为false
mState.mLayoutStep
是不是等于State.STEP_START,由于之前在onMeasure
中已经设置为State.STEP_ANIMATIONS,所以这里进不去,从我实际debug来看,最后会跑进else if
这个代码块,也就是LayoutManager中的尺寸和RecyclerView的尺寸不一样,需要重新测量,就是调用mLayout.setExactMeasureSpecsFrom(this)
方法,将RecyclerView的尺寸传给LayoutManager,然后再跑一边dispatchLayoutStep2()
方法。dispatchLayoutStep3()
,dispatchLayoutStep3
的注释是说该方法是layout的最后一步,主要是保存view的动画信息,启动动画,做一些必要的清理工作。好了还是贴源码,比较多,他妈也不算多,跟framework一个方法动不动就几百行来说真是小意思。 /**
* The final step of the layout where we save the information about views for animations,
* trigger animations and do any necessary cleanup.
*/
private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
eatRequestLayout();
onEnterLayoutOrScroll();
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
...
}
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;
mLayout.mRequestedSimpleAnimations = false;
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
// Initial prefetch has expanded cache, so reset until next prefetch.
// This prevents initial prefetches from expanding the cache permanently.
mLayout.mPrefetchMaxCountObserved = 0;
mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
mRecycler.updateViewCacheSize();
}
mLayout.onLayoutCompleted(mState);
onExitLayoutOrScroll();
resumeRequestLayout(false);
mViewInfoStore.clear();
if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
dispatchOnScrolled(0, 0);
}
recoverFocusFromState();
resetFocusInfo();
}
我们直接跳到if(mState.mRunSimpleAnimations)
因为这是第一次运行,所以不会跑动画,这个if
进不去,我就把代码去掉了,在后面的文章中再仔细研究。接下来直接跳到mLayout.removeAndRecycleScrapInt(mRecycler)
此方法将scrap的view从RecyclerView移除,并回收到Recycler中。接下来跳到mLayout.onLayoutCompleted(mState)
此方法说明到此RecyclerView的layout工作已经完成了,LayoutManager如果需要做一些事情,可以重写这个方法,此方法只有在RecyclerView的onLayout
方法末尾才会被调用。再跳到
if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
dispatchOnScrolled(0, 0);
}
上述代码其实是判断RecyclerView有没有被滑动过,如果有,则调用那些监听scroll的Listener
最后恢复焦点,重置mState
中关于焦点信息的对象。
好了,dispatchLayoutStep3()
方法也说完了,因为我们跳过了动画的代码,所以比较简单,先留着吧,后面会再来讲,dispatchLayoutStep3()
跑完,onLayout
方法也跑完了,接下来就是onDraw
了
onDraw
此方法专门用于绘制RecyclerView本身的内容,不包括item,因为item会调用自身的onDraw方法。
直接贴代码吧
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
呵呵简单的不可思议,竟然只画了ItemDecoration的内容,而且只是调用了ItemDecoration的onDraw
方法,那么问题来了,据我所知,ItemDecoration还有一个onDrawOver
方法,那这个方法是什么时候掉的呢?当然是搜一下就好了,原来RecyclerView还实现了draw
方法,而这个onDrawOver
方法就是在这个方法里被调用的,代码如下
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
其实按照一般自定义view的方法,我们是不需要实现这个draw方法的,只需要实现onDraw
方法,那么为什么这里要实现draw
方法呢?那就看下这里draw
方法做了什么事情,首先我们要清楚,在draw
方法的最开始调用了super.draw
也就是View的draw
方法,而view的draw
方法会调用onDraw
方法,所以在这里整个方法的调用顺序是这样的:View.draw->RecycleView.onDraw->RecycleView.draw
。好下来看一下draw
方法做了什么,代码有点多,选重要的贴
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
首先调用super.draw
,然后调用所有ItemDecoration的onDrawOver
方法,所以从这里我们就可以知道,ItemDecoration的onDraw
方法是画在item之前,而onDrawOver
是画在item之后。
接下来
boolean needsInvalidate = false;
if (mLeftGlow != null && !mLeftGlow.isFinished()) {
final int restore = c.save();
final int padding = mClipToPadding ? getPaddingBottom() : 0;
c.rotate(270);
c.translate(-getHeight() + padding, 0);
needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
c.restoreToCount(restore);
}
if (mTopGlow != null && !mTopGlow.isFinished()) {
final int restore = c.save();
if (mClipToPadding) {
c.translate(getPaddingLeft(), getPaddingTop());
}
needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);
c.restoreToCount(restore);
}
if (mRightGlow != null && !mRightGlow.isFinished()) {
final int restore = c.save();
final int width = getWidth();
final int padding = mClipToPadding ? getPaddingTop() : 0;
c.rotate(90);
c.translate(-padding, -width);
needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
c.restoreToCount(restore);
}
if (mBottomGlow != null && !mBottomGlow.isFinished()) {
final int restore = c.save();
c.rotate(180);
if (mClipToPadding) {
c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom());
} else {
c.translate(-getWidth(), -getHeight());
}
needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);
c.restoreToCount(restore);
}
// If some views are animating, ItemDecorators are likely to move/change with them.
// Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
// display lists are not invalidated.
if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
&& mItemAnimator.isRunning()) {
needsInvalidate = true;
}
if (needsInvalidate) {
ViewCompat.postInvalidateOnAnimation(this);
}
其实就是画了RecyclerView四个边的边界效果,而且如果边界动画还没结束就要继续调用postInvalidateOnAnimation
方法,来进行重绘,从而实现动画效果。
好啦,RecycleView的绘制过程就到这里啦,由于我也是在学习源码阶段,有些地方不是很清楚,只能先留着等全部看明白后再来补充