从今天开始,楼主正式开始分析RecyclerView
的源码。为了阅读RecyclerView
的源码,楼主专门去看了View
的三大流程,也就是所谓的刷装备。当然在阅读RecyclerView
的源码时,也参考了其他大佬的文章,本文尽可能的贴出比较优秀的文章,正所谓他山之石,可以攻玉。
作为系列的第一篇文章,说说楼主为什么需要来专门的阅读RecyclerView
的源码,主要从三大方面说起。一是RecyclerView
在实际开发非常的重要,现在几乎每个app都会展示很多的数据,列表展示自然是非常好的方式,而在RecyclerView
在列表中占据着举足轻重的作用,所以RecyclerView
在实际开发中,是经常见的,我们得之鱼,还必须得之渔;二是现在网上分析RecyclerView的文章满篇飞,但是文章大多都比较零碎,没有系统的分析RecyclerView
,本文打算系统的分析RecyclerView
,也可以说是集百家之长;三是楼主本身对RecycleView
的使用也是非常的频繁,但是没有深入的了解它的原理,所以这也算是对自身的一个提升。
阅读源码本身是一件非常枯燥和耗时间的事情,对楼主自身来说,也是亚历山大,害怕自己自身的经验不足,误导前来学习的同学,所以如果文章中有错误的地方,请各位大佬指点。
本系列文章楼主打算从几个地方说起。先是将RecyclerView
当成一个普通的View
,分别分析它的三大流程、事件传递(包括嵌套滑动);然后是分析RecyclerView
的缓存原理,这也是RecyclerView
的精华所在;然后分析的是RecyclerView
的Adapter
、LayoutManager
、ItemAnimator
和ItemDecoration
。最后就是RecyclerView
的扩展,包括LayoutManager
的自定义和使用RecyclerView
常见的坑等。
看到上面所写的列表,自己也不禁留下冷汗,原来RecyclerView
有这么多的内容,真担心自己不能完成任务。
1. 概述
在分析RecyclerView
源码之前,我们还是对RecyclerView
有一个初步的了解,简单的了解它是什么,它的基本结构有哪些。
RecyclerView
是Google爸爸在2014年的IO大会提出来(看来RecyclerView
的年龄还是比较大了),具体目的是不是用来替代ListView
的,楼主也不知道,因为那时候楼主还在读高二。但是在实际开发中,自从有了RecyclerView
,ListView
和GridView
就很少用了,所以我们暂且认为RecyclerView
的目的是替代ListView
和GridView
。
RecyclerView
本身是一个展示大量数据的控件,相比较ListView
,RecyclerView
的4级缓存(也有人说是3级缓存,这些都不重要)就表现的非常出色,在性能方面相比于ListView
提升了不少。同时由于LayoutManager
的存在,让RecyclerView
不仅有ListView
的特点,同时兼有GridView
的特点。这可能是RecyclerView
受欢迎的原因之一吧。
RecyclerView
在设计方面上也是非常的灵活,不同的部分承担着不同的职责。其中Adapter
负责提供数据,包括创建ViewHolder
和绑定数据,LayoutManager
负责ItemView
的测量和布局,ItemAnimator
负责每个ItemView
的动画,ItemDecoration
负责每个ItemView
的间隙。这种插拔式的架构使得RecyclerView
变得非常的灵活,每一个人都可以根据自身的需求来定义不同的部分。
正因为这种插拔式的设计,使得RecyclerView
在使用上相比较于其他的控件稍微难那么一点点,不过这都不算事,谁叫RecyclerView
这么惹人爱呢。
好了,好像废话有点多,现在我们正式来分析源码吧,本文的重点是RecyclerView
的三大流程。
本文参考文章:
注意,本文RecyclerView
源码均来自于27.1.1
2. measure
不管RecyclerView
是多么神奇,它也是一个View
,所以分析它的三大流程是非常有必要的。同时,如果了解过RecyclerView
的同学应该都知道,RecyclerView
的三大流程跟普通的View
比较,有很大的不同。
首先,我们来看看measure过程,来看看RecyclerView
的onMeasure
方法。
protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { // 第一种情况 } if (mLayout.isAutoMeasureEnabled()) { // 第二种情况 } else { // 第三种情况 } }
onMeasure
方法还是有点长,这里我将它分为3种情况,我将简单解释这三种情况。
mLayout
即LayoutManager
的对象。我们知道,当RecyclerView
的LayoutManager
为空时,RecyclerView
不能显示任何的数据,在这里我们找到答案。
LayoutManager
开启了自动测量时,这是一种情况。在这种情况下,有可能会测量两次。第三种情况就是没有开启自动测量的情况,这种情况比较少,因为为了
RecyclerView
支持warp_content
属性,系统提供的LayoutManager
都开启自动测量的,不过我们还是要分析的。
首先我们来第一种情况。
(1).当LayoutManager为空时
这种情况下比较简单,我们来看看源码:
if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); return; }
直接调了defaultOnMeasure
方法,我们继续来看defaultOnMeasure
方法。
void defaultOnMeasure(int widthSpec, int heightSpec) { // calling LayoutManager here is not pretty but that API is already public and it is better // than creating another method since this is internal. final int width = LayoutManager.chooseSize(widthSpec, getPaddingLeft() + getPaddingRight(), ViewCompat.getMinimumWidth(this)); final int height = LayoutManager.chooseSize(heightSpec, getPaddingTop() + getPaddingBottom(), ViewCompat.getMinimumHeight(this)); setMeasuredDimension(width, height); }
在defaultOnMeasure
方法里面,先是通过LayoutManager
的chooseSize
方法来计算值,然后就是setMeasuredDimension
方法来设置宽高。我们来看看:
public static int chooseSize(int spec, int desired, int min) { final int mode = View.MeasureSpec.getMode(spec); final int size = View.MeasureSpec.getSize(spec); switch (mode) { case View.MeasureSpec.EXACTLY: return size; case View.MeasureSpec.AT_MOST: return Math.min(size, Math.max(desired, min)); case View.MeasureSpec.UNSPECIFIED: default: return Math.max(desired, min); } }
chooseSize
方法表达的意思比较简单,就是通过RecyclerView
的测量mode来获取不同的值,这里就不详细的解释了。
到此,第一种情况就分析完毕了。因为当LayoutManager
为空时,那么当RecyclerView
处于onLayout
阶段时,会调用dispatchLayout
方法。而在dispatchLayout
方法里面有这么一行代码:
if (mLayout == null) { Log.e(TAG, "No layout manager attached; skipping layout"); // leave the state in START return; }
所以,当LayoutManager
为空时,不显示任何数据是理所当然的。
现在我们来看看第二种情况,也就是正常的情况。
(2). 当LayoutManager开启了自动测量
在分析这种情况之前,我们先对了解几个东西。
RecyclerView
的测量分为两步,分别调用dispatchLayoutStep1
和dispatchLayoutStep2
。同时,了解过RecyclerView
源码的同学应该知道在RecyclerView
的源码里面还一个dispatchLayoutStep3
方法。这三个方法的方法名比较接近,所以容易让人搞混淆。本文会详细的讲解这三个方法的作用。
由于在这种情况下,只会调用dispatchLayoutStep1
和dispatchLayoutStep2
这两个方法,所以这里会重点的讲解这两个方法。而dispatchLayoutStep3
方法的调用在RecyclerView
的onLayout
方法里面,所以在后面分析onLayout
方法时再来看dispatchLayoutStep3
方法。
我们在分析之前,先来看一个东西--mState.mLayoutStep
。这个变量有几个取值情况。我们分别来看看:
取值 | 含义 |
---|---|
State.STEP_START | mState.mLayoutStep 的默认值,这种情况下,表示RecyclerView还未经历dispatchLayoutStep1 ,因为dispatchLayoutStep1 调用之后mState.mLayoutStep 会变为State.STEP_LAYOUT 。 |
State.STEP_LAYOUT | 当mState.mLayoutStep 为State.STEP_LAYOUT 时,表示此时处于layout阶段,这个阶段会调用dispatchLayoutStep2 方法layout RecyclerView 的children 。调用dispatchLayoutStep2 方法之后,此时mState.mLayoutStep 变为了State.STEP_ANIMATIONS 。 |
State.STEP_ANIMATIONS | 当mState.mLayoutStep 为State.STEP_ANIMATIONS 时,表示RecyclerView 处于第三个阶段,也就是执行动画的阶段,也就是调用dispatchLayoutStep3 方法。当dispatchLayoutStep3 方法执行完毕之后,mState.mLayoutStep 又变为了State.STEP_START 。 |
从上表中,我们了解到mState.mLayoutStep
的三个状态对应着不同的dispatchLayoutStep
方法。这一点,我们必须清楚,否则接下来的代码将难以理解。
好了,前戏准备的差不多,现在应该进入高潮了。我们开始正式的分析源码了。
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); } }
我将这段代码分为三步。我们来看看:
调用
LayoutManager
的onMeasure
方法进行测量。对于onMeasure
方法,我也感觉到非常的迷惑,发现传统的LayoutManager
都没有实现这个方法。后面,我们会将简单的看一下这个方法。如果
mState.mLayoutStep
为State.STEP_START
的话,那么就会执行dispatchLayoutStep1
方法,然后会执行dispatchLayoutStep2
方法。如果需要第二次测量的话,会再一次调用
dispatchLayoutStep2
方法。
以上三步,我们一步一步的来分析。首先,我们来看看第一步,也是看看onMeasure
方法。
LayoutManager
的onMeasure
方法究竟为我们做什么,我们来看看:
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); }
默认是调用的RecyclerView
的defaultOnMeasure
方法,至于defaultOnMeasure
方法里面究竟做了什么,这在前面已经介绍过了,这里就不再介绍了。
View
的onMeasure
方法的作用通产来说有两个。一是测量自身的宽高,从RecyclerView
来看,它将自己的测量工作托管给了LayoutManager
的onMeasure
方法。所以,我们在自定义LayoutManager
时,需要注意onMeasure
方法存在,不过从官方提供的几个LayoutManager
,都没有重写这个方法。所以不到万得已,最好不要重写LayoutManager
的onMeasure
方法;二是测量子View
,不过到这里我们还没有看到具体的实现。
接下来,我们来分析第二步,看看dispatchLayoutStep1
方法和dispatchLayoutStep2
方法究竟做了什么。
在正式分析第二步之前,我们先对这三个方法有一个大概的认识。
方法名 | 作用 |
---|---|
dispatchLayoutStep1 | 三大dispatchLayoutStep 方法第一步。本方法的作用主要有三点:1.处理Adapter 更新;2.决定是否执行ItemAnimator ;3.保存ItemView 的动画信息。本方法也被称为preLayout(预布局),当Adapter 更新了,这个方法会保存每个ItemView 的旧信息(oldViewHolderInfo) |
dispatchLayoutStep2 | 三大dispatchLayoutStep 方法第二步。在这个方法里面,真正进行children 的测量和布局。 |
dispatchLayoutStep3 | 三大dispatchLayoutStep 方法第三步。这个方法的作用执行在dispatchLayoutStep1 方法里面保存的动画信息。本方法不是本文的介绍重点,后面在介绍ItemAnimator 时,会重点分析这个方法。 |
我们回到onMeasure方法里面,先看看整个执行过程。
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();
如果mState.mLayoutStep == State.STEP_START
时,才会调用 dispatchLayoutStep1
方法,这里与我们前面介绍mLayoutStep
对应起来了。现在我们看看dispatchLayoutStep1
方法
private void dispatchLayoutStep1() { mState.assertLayoutStep(State.STEP_START); fillRemainingScrollValues(mState); mState.mIsMeasuring = false; startInterceptRequestLayout(); mViewInfoStore.clear(); onEnterLayoutOrScroll(); processAdapterUpdatesAndSetAnimationFlags(); saveFocusInfo(); mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged; mItemsAddedOrRemoved = mItemsChanged = false; mState.mInPreLayout = mState.mRunPredictiveAnimations; mState.mItemCount = mAdapter.getItemCount(); findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); if (mState.mRunSimpleAnimations) { // 找到没有被remove的ItemView,保存OldViewHolder信息,准备预布局 } if (mState.mRunPredictiveAnimations) { // 进行预布局 } else { clearOldPositions(); } onExitLayoutOrScroll(); stopInterceptRequestLayout(false); mState.mLayoutStep = State.STEP_LAYOUT; }
本文只简单分析一下这个方法,因为这个方法跟ItemAnimator
有莫大的关系,后续在介绍ItemAnimator
时会详细的分析。在这里,我们将重点放在processAdapterUpdatesAndSetAnimationFlags
里面,因为这个方法计算了mRunSimpleAnimations
和mRunPredictiveAnimations
。
private void processAdapterUpdatesAndSetAnimationFlags() { if (mDataSetHasChangedAfterLayout) { // Processing these items have no value since data set changed unexpectedly. // Instead, we just reset it. mAdapterHelper.reset(); if (mDispatchItemsChangedEvent) { 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(); }
这里我们的重心放在mFirstLayoutComplete
变量里面,我们发现mRunSimpleAnimations
的值与mFirstLayoutComplete
有关,mRunPredictiveAnimations
同时跟mRunSimpleAnimations
有关。所以这里我们可以得出一个结论,当RecyclerView
第一次加载数据时,是不会执行的动画。换句话说,每个ItemView
还没有layout
完毕,怎么会进行动画。这一点,我们也可以通过Demo来证明,这里也就不展示了。
接下来我们看看dispatchLayoutStep2
方法,这个方法是真正布局children
。我们来看看:
private void dispatchLayoutStep2() { startInterceptRequestLayout(); 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(); stopInterceptRequestLayout(false); }
在这里,我们重点的看两行代码。一是在这里,我们可以看到Adapter
的getItemCount
方法被调用;二是调用了LayoutManager
的onLayoutChildren
方法,这个方法里面进行对children
的测量和布局,同时这个方法也是这里的分析重点。
系统的LayoutManager
的onLayoutChildren
方法是一个空方法,所以需要LayoutManager
的子类自己来实现。从这里,我们可以得出两个点。
子类
LayoutManager
需要自己实现onLayoutChildren
方法,从而来决定RecyclerView
在该LayoutManager
的策略下,应该怎么布局。从这里,我们看出来RecyclerView
的灵活性。
LayoutManager
类似于ViewGroup
,将onLayoutChildren
方法(ViewGroup
是onLayout
方法)公开出来,这种模式在Android中很常见的。
这里,我先不对onLayoutChildren
方法进行展开,待会会详细的分析。
接下来,我们来分析第三种情况--没有开启自动测量。
(3).没有开启自动测量
我们先来看看这一块的代码。
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
例如上面的代码,我将分为2步:
如果
mHasFixedSize
为true(也就是调用了setHasFixedSize
方法),将直接调用LayoutManager
的onMeasure
方法进行测量。如果
mHasFixedSize
为false,同时此时如果有数据更新,先处理数据更新的事务,然后调用LayoutManager
的onMeasure
方法进行测量
通过上面的描述,我们知道,如果未开启自动测量,那么肯定会调用LayoutManager
的onMeasure
方法来进行测量,这就是LayoutManager
的onMeasure
方法的作用。
至于onMeasure
方法怎么进行测量,那就得看LayoutManager
的实现类。在这里,我们就不进行深入的追究了。
3. layout
measure
过程分析的差不多了,接下来我们就该分析第二个过程--layout
。我们来看看onLayout
方法:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); mFirstLayoutComplete = true; }
onLayout
方法本身没有做多少的事情,重点还是在dispatchLayout
方法里面。
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(); }
dispatchLayout
方法也是非常的简单,这个方法保证RecyclerView
必须经历三个过程--dispatchLayoutStep1
、dispatchLayoutStep2
、dispatchLayoutStep3
。
同时,在后面的文章中,你会看到dispatchLayout
方法其实还为RecyclerView
节省了很多步骤,也就是说,在RecyclerView
经历一次完整的dispatchLayout
之后,后续如果参数有所变化时,可能只会经历最后的1步或者2步。当然这些都是后话了。
对于dispatchLayoutStep1
和dispatchLayoutStep2
方法,我们前面已经讲解了,这里就不做过多的解释了。这里,我们就简单的看一下dispatchLayoutStep3
方法吧。
private void dispatchLayoutStep3() { // ······ mState.mLayoutStep = State.STEP_START; // ······ }
为什么这里只是简单看一下dispatchLayoutStep3
方法呢?因为这个方法主要是做Item的动画,也就是我们熟知的ItemAnimator
的执行,而本文不对动画进行展开,所以先省略动画部分。
在这里,我们需要关注dispatchLayoutStep3
方法的是,它将mLayoutStep
重置为了State.STEP_START
。也就是说如果下一次重新开始dispatchLayout
的话,那么肯定会经历dispatchLayoutStep1
、dispatchLayoutStep2
、dispatchLayoutStep3
三个方法。
以上就是RecyclerView
的layout过程,是不是感觉非常的简单?RecyclerView
跟其他ViewGroup
不同的地方在于,如果开启了自动测量,在measure
阶段,已经将Children
布局完成了;如果没有开启自动测量,则在layout
阶段才布局Children
。
4. draw
接下来,我们来分析三大流程的最后一个阶段--draw
。在正式分析draw过程之前,我先来对RecyclerView
的draw
做一个概述。
RecyclerView
分为三步,我们来看看:
调用
super.draw
方法。这里主要做了两件事:1. 将Children
的绘制分发给ViewGroup
;2. 将分割线的绘制分发给ItemDecoration
。如果需要的话,调用
ItemDecoration
的onDrawOver
方法。通过这个方法,我们在每个ItemView
上面画上很多东西。如果
RecyclerView
调用了setClipToPadding
,会实现一种特殊的滑动效果--每个ItemView可以滑动到padding区域。
我们来看看这部分的代码:
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); } // 第三步 // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we // need find children closest to edges. Not sure if it is worth the effort. // ······ }
熟悉三大流程的同学,肯定知道第一步会回调到onDraw
方法里面,也就是说关于Children
的绘制和ItemDecoration
的绘制,是在onDraw
方法里面。
@Override 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); } }
onDraw
方法是不是非常的简单?调用super.onDraw
方法将Children
的绘制分发给ViewGroup
执行;然后将ItemDecoration
的绘制分发到ItemDecoration
的onDraw
方法里面去。从这里,我们可以看出来,RecyclerView
的设计实在是太灵活了!
至于其余两步都比较简单,这里就不详细分析了。不过,从这里,我们终于明白了ItemDecoration
的onDraw
方法和onDrawOver
方法的区别。
5. LayoutManager的onLayoutChildren方法
从整体来说,RecyclerView
的三大流程还是比较简单,不过在整个过程中,我们似乎忽略了一个过程--那就是RecyclerView
到底是怎么layout children
的?
前面在介绍dispatchLayoutStep2
方法时,只是简单的介绍了,RecyclerView
通过调用LayoutManager
的onLayoutChildren
方法。LayoutManager
本身对这个方法没有进行实现,所以必须得看看它的子类,这里我们就来看看LinearLayoutManager
。
由于LinearLayoutManager
的onLayoutChildren
方法比较长,这里不可能贴出完整的代码,所以这里我先对这个方法做一个简单的概述,方便大家理解。
确定锚点的信息,这里面的信息包括:1.
Children
的布局方向,有start和end两个方向;2.mPosition
和mCoordinate
,分别表示Children
开始填充的position和坐标。调用
detachAndScrapAttachedViews
方法,detach
掉或者remove
掉RecyclerView
的Children
。这一点本来不在本文的讲解范围内,但是为了后续对RecyclerView
的缓存机制有更好的了解,这里特别的提醒一下。根据锚点信息,调用
fill
方法进行Children
的填充。这个过程中根据锚点信息的不同,可能会调用两次fill
方法。
接下来,我们看看代码:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { // layout algorithm: // 1) by checking children and other variables, find an anchor coordinate and an anchor // item position. // 2) fill towards start, stacking from bottom // 3) fill towards end, stacking from top // 4) scroll to fulfill requirements like stack from bottom. // create layout state // ······ // 第一步 final View focused = getFocusedChild(); if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION || mPendingSavedState != null) { mAnchorInfo.reset(); mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; // calculate anchor position and coordinate updateAnchorInfoForLayout(recycler, state, mAnchorInfo); mAnchorInfo.mValid = true; } // ······ // 第二步 detachAndScrapAttachedViews(recycler); mLayoutState.mIsPreLayout = state.isPreLayout(); // 第三步 if (mAnchorInfo.mLayoutFromEnd) { // fill towards start updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtra = extraForStart; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; final int firstElement = mLayoutState.mCurrentPosition; if (mLayoutState.mAvailable > 0) { extraForEnd += mLayoutState.mAvailable; } // fill towards end updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtra = extraForEnd; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; if (mLayoutState.mAvailable > 0) { // end could not consume all. add more items towards start extraForStart = mLayoutState.mAvailable; updateLayoutStateToFillStart(firstElement, startOffset); mLayoutState.mExtra = extraForStart; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; } } else { // fill towards end updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtra = extraForEnd; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; final int lastElement = mLayoutState.mCurrentPosition; if (mLayoutState.mAvailable > 0) { extraForStart += mLayoutState.mAvailable; } // fill towards start updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtra = extraForStart; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; if (mLayoutState.mAvailable > 0) { extraForEnd = mLayoutState.mAvailable; // start could not consume all it should. add more items towards end updateLayoutStateToFillEnd(lastElement, endOffset); mLayoutState.mExtra = extraForEnd; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; } } // ······ }
相信从上面的代码都可以找出每一步的执行。现在,我们来详细分析每一步。首先来看第一步--确定锚点的信息
。
要想看锚点信息的计算过程,我们可以从updateAnchorInfoForLayout
方法里面来找出答案,我们来看看updateAnchorInfoForLayout
方法:
private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo) { // 第一种计算方式 if (updateAnchorFromPendingData(state, anchorInfo)) { return; } // 第二种计算方式 if (updateAnchorFromChildren(recycler, state, anchorInfo)) { return; } // 第三种计算方式 anchorInfo.assignCoordinateFromPadding(); anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0; }
我相信通过上面的代码注释,大家都能明白updateAnchorInfoForLayout
方法到底干了嘛,这里我简单分析一下这三种确定所做的含义,具体是怎么做的,这里就不讨论,因为这里面的细节太多了,深入的讨论容易将我们聪明无比的大脑搞晕。
第一种计算方式,表示含义有两种:1.
RecyclerView
被重建,期间回调了onSaveInstanceState
方法,所以目的是为了恢复上次的布局;2.RecyclerView
调用了scrollToPosition
之类的方法,所以目的是让RecyclerView
滚到准确的位置上去。所以,锚点的信息根据上面的两种情况来计算。第二种计算方法,从
Children
上面来计算锚点信息。这种计算方式也有两种情况:1. 如果当前有拥有焦点的Child
,那么有当前有焦点的Child的位置来计算锚点;2. 如果没有child拥有焦点,那么根据布局方向(此时布局方向由mLayoutFromEnd
来决定)获取可见的第一个ItemView
或者最后一个ItemView
。如果前面两种方式都计算失败了,那么采用第三种计算方式,也就是默认的计算方式。
以上就是updateAnchorInfoForLayout
方法所做的事情,这里就不详细纠结每种计算方式的细节,有兴趣的同学可以看看。
至于第二步,调用detachAndScrapAttachedViews
方法对所有的ItemView
进行回收,这部分的内容属于RecyclerView
缓存机制的部分,本文先在这里埋下一个伏笔,后续专门讲解RecyclerView
会详细的分析它,所以这里就不讲解了。
接下来我们来看看第三步,也就是调用fill
方法来填充Children
。在正式分析填充过程时,我们先来看一张图片:
作者:琼珶和予
链接:https://www.jianshu.com/p/61fe3f3bb7ec