RecyclerView 源码分析(一)
2017-11-16 20:36
1561 查看
构造函数
mChildHelper
mAdapterHelper
LayoutManager
LinearLayoutManager
setLayoutManager
ItemDecoration
DividerItemDecoration
addItemDecoration
Adapter
onMeasure
onLayout
dispatchLayoutStep1
dispatchLayoutStep2
draw
总结
在工作上,越来越多的使用
本文分析的版本为
本篇文章只分析
在看这篇文章前,需要对
设置
设置适配器
设置
构造函数通常都是初始化一些属性的,当然这里也不例外。基于本文分析的侧重点,我列出几个重要的变量的初始化:
这2个变量在后面的分析大量被使用,所以,我在后面列出了这2个变量初始化的源码,以供查询参考。
另外,
还有一点需要提一下,就是可以在 XML 中,为
那么,我们就以这一段代码开始分析。
如果只用一个参数的构造方法,其实调用了三个参数的构造方法,也就是默认创建了垂直方向的
因此,如果你想要创建水平方向的
最后一个参数
最终,这个构造函数只是初始化了三个变量
由于才刚刚设置
首先看下
很简单,这里为
再回到
这里就只是为
按照这个代码进行分析。
老规矩,初始化变量
从这段代码的 Log 信息中可以看到,可以通过调用
例子中调用的是一个参数的构造方法,默认调用 了两个参数的构造方法,
纵观整个方法,分了三步,我已经用注释标明了。
最主要的就是第一步,由于
可见顺序很重要,因为影响了绘制的顺序,这个后面可以看到。
最后一步调用了
到这里为止,好像并没有把加数加载进去。确实没有,因为我们需要再次刷新布局,这也就是为什么
可以看到,如果
可以看到最终测量的策略采用的是
从这里可以看出,如果给
而如果设置为了
到这里,就真正的开始布局了,分为了三步
dispatchLayoutStep1()
dispatchLayoutStep2()
dispatchLayoutStep3()
从注释中就可以看出,
第二步之前还调用了
这里就是为
以
这个方法内部写明了步骤,但是代码很长,那现在来一步步抽丝剥茧。
很明显,调用的是
再接着看
再接着看下
变量
前两个 if 语句,因为条件不够,并没有做任何事情,这里跳过分析。
而
现在再来看看
由于我们现在的分析还没有涉及到滚动或者动画,因此
最后两行,还对
实际上都是调用
现在再来看下
现在再来看下
那么现在进入到
在
现在继续看
前面已经对
看看
可以看到,
ok,循环条件已经看完,直接进入
首先用
我把
先看看第三步,
第一步是从
第二步就是从
代码注释我并没有省略,可能刚开始看很难理解,但是只要你分析完代码,再看注释,就会有种恍然大悟的感觉。
代码我做了精简,可以看到有好多个地方获取 ViewHolder。 由于本文分析的情况是刚开始加载,所以需要用 Adapter 来创建,也就是
调用实现类的
设置
创建了
精简代码后,就清晰了吧,就是调用了
给
如果
调用
调用
给
绑定完了之后还没有完,需要给
由于在
那么,就走到了
很显然,
再回到
由于此时的
可以看到创建的
这两行,计算了已经使用的宽度和高度。 纳尼!所以从
计算完已经使用的宽高后,就需要来计算
由于
而测量的策略采用的是
完成了这些之后就需要让
现在还没有对获取的
这段代码很简单,
然后计算
这段代码就不多做解释了, 计算后的
left = mRecyclerView.getPaddingLeft()
right = left + child.getMeasuredWidth() + insets.left + insets.right + params.leftMargin + params.rightMargin
top = layoutState.mOffset,也就是 mRecyclerView.getPaddingTop()
bottom = mRecyclerView.getPaddingTop() + result.mConsumed。 result.mConsumed 刚才已经计算出来了。。
有了
这个就很直观了,不用解释了。
至此
1. get view
2. add view
3. measure view
4. layout view
这四步是不是看起来很舒服~
那么,现在再回到 fill() 的方法,我再做了精简
直接看
然后,
再然后,
这一切准备完成后,就进行下一次的 while 循环了,直到
从
第一个
注意
至此,
现在再返回到
从注释中看有两步,现在已经分析完了 “fill towards end” 这一步的
接下来就是 “fill towards start”,从底部往头部填充,这种情况并不适合现在分析的状况,所以这里就不分析了。
然后,
那么,至此,
那么现在看 ReyclerView 的 draw 过程了。
在
调用完
mChildHelper
mAdapterHelper
LayoutManager
LinearLayoutManager
setLayoutManager
ItemDecoration
DividerItemDecoration
addItemDecoration
Adapter
onMeasure
onLayout
dispatchLayoutStep1
dispatchLayoutStep2
draw
总结
在工作上,越来越多的使用
RecyclerView来代替
ListView和
GridView,更有甚者,我发现有人想用
RecyclerView来替代
ViewPager,但是前提是要解决预加载的问题。然而,我并不只想从表面去使用它,我需要知根知底,这样才能以不变应万变。
本文分析的版本为
compile 'com.android.support:recyclerview-v7:26.1.0'
本篇文章只分析
RecyclerView首次加载并显示的过程。而动画原理,回收机制留到后面文章分析。所以分析源码的时候,就有侧重点,这样才好分析。
在看这篇文章前,需要对
RecyclerView的使用有一个基本的认识,使用
RecyclerView基本三要素如下:
设置
LayoutManager,用来
measure,
layout从
Recycler获取到的
View。其实
LayoutManager还有负责
RecyclerView的测量,这在后面的分析中将会看到。
设置适配器
Adapter,也就是
RecyclerView.Adapter,用来将数据转化为
RecyclerView可用的
View。
设置
ItemDecoration,这个是可选的,一般用于绘制分割线,系统默认为
LinearLayoutManager配置了一个
DividerItemDecoration。
构造函数
说了这么多,那么从哪里开始分析起呢?RecyclerView首次是从 XML 加载,当然先看构造函数。
public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if (attrs != null) { TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0); // 如果不设置clipToPadding,默认就是为 true mClipToPadding = a.getBoolean(0, true); a.recycle(); } else { mClipToPadding = true; } // 可根据控件大小自动伸缩 setScrollContainer(true); setFocusableInTouchMode(true); final ViewConfiguration vc = ViewConfiguration.get(context); mTouchSlop = vc.getScaledTouchSlop(); mScaledHorizontalScrollFactor = ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context); mScaledVerticalScrollFactor = ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context); mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); mItemAnimator.setListener(mItemAnimatorListener); // 给 mAdapterHelper 赋值 initAdapterManager(); // 给 mChildHelper 赋值 initChildrenHelper(); // If not explicitly specified this view is important for accessibility. if (ViewCompat.getImportantForAccessibility(this) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); } mAccessibilityManager = (AccessibilityManager) getContext() .getSystemService(Context.ACCESSIBILITY_SERVICE); setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); // Create the layoutManager if specified. boolean nestedScrollingEnabled = true; if (attrs != null) { int defStyleRes = 0; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, defStyle, defStyleRes); String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager); int descendantFocusability = a.getInt( R.styleable.RecyclerView_android_descendantFocusability, -1); if (descendantFocusability == -1) { setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); } // 可以设置 fastScrollEnabled 属性,但是同时要设置下面四个属性 // TODO: 有没有高级的用法? mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false); if (mEnableFastScroller) { StateListDrawable verticalThumbDrawable = (StateListDrawable) a .getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable); Drawable verticalTrackDrawable = a .getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable); StateListDrawable horizontalThumbDrawable = (StateListDrawable) a .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable); Drawable horizontalTrackDrawable = a .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable); initFastScroller(verticalThumbDrawable, verticalTrackDrawable, horizontalThumbDrawable, horizontalTrackDrawable); } a.recycle(); // 需要在属性中声明layoutManager才能创建 LayoutManager 对象 createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes); if (Build.VERSION.SDK_INT >= 21) { a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS, defStyle, defStyleRes); // nestedScrollingEnabled 默认为 true nestedScrollingEnabled = a.getBoolean(0, true); a.recycle(); } } else { setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); } // Re-set whether nested scrolling is enabled so that it is set on all API levels setNestedScrollingEnabled(nestedScrollingEnabled); }
构造函数通常都是初始化一些属性的,当然这里也不例外。基于本文分析的侧重点,我列出几个重要的变量的初始化:
initAdapterManager()初始化了
mAdapterHelper变量
initChildrenHelper()初始化了
mChildHelper变量
这2个变量在后面的分析大量被使用,所以,我在后面列出了这2个变量初始化的源码,以供查询参考。
另外,
RecyclerView一直被人诟病的没有
FastScroller的属性,这个版本也有了。不过要同时设置5个属性! 你没看错,是5个属性,具体如何使用,请参考 https://stackoverflow.com/questions/45370246/how-to-use-fastscrollenabled-in-recyclerview。 不过,我试了下,功能好像还是不完全,例如
ListView的
Adapter如果实现了
SectionIndexer接口,
ListView的
FastScroller就可以通过滚动显示一个索引, 具体效果,参见 7.0 版本的
Launcher的效果。
还有一点需要提一下,就是可以在 XML 中,为
RecyclerView设置
android:layoutManager属性,然后系统根据这个名字,通过反射创建
LayoutManager对象。不过,我们通常都是动态去设置
LayoutManager,所以这里就不去分析 XML 设置
LayoutManager的情况。
mChildHelper
private void initChildrenHelper() { mChildHelper = new ChildHelper(new ChildHelper.Callback() { @Override public int getChildCount() { return RecyclerView.this.getChildCount(); } @Override public void addView(View child, int index) { if (VERBOSE_TRACING) { TraceCompat.beginSection("RV addView"); } RecyclerView.this.addView(child, index); if (VERBOSE_TRACING) { TraceCompat.endSection(); } dispatchChildAttached(child); } @Override public int indexOfChild(View view) { return RecyclerView.this.indexOfChild(view); } @Override public void removeViewAt(int index) { final View child = RecyclerView.this.getChildAt(index); if (child != null) { dispatchChildDetached(child); // Clear any android.view.animation.Animation that may prevent the item from // detaching when being removed. If a child is re-added before the // lazy detach occurs, it will receive invalid attach/detach sequencing. child.clearAnimation(); } if (VERBOSE_TRACING) { TraceCompat.beginSection("RV removeViewAt"); } RecyclerView.this.removeViewAt(index); if (VERBOSE_TRACING) { TraceCompat.endSection(); } } @Override public View getChildAt(int offset) { return RecyclerView.this.getChildAt(offset); } @Override public void removeAllViews() { final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); dispatchChildDetached(child); // Clear any android.view.animation.Animation that may prevent the item from // detaching when being removed. If a child is re-added before the // lazy detach occurs, it will receive invalid attach/detach sequencing. child.clearAnimation(); } RecyclerView.this.removeAllViews(); } @Override public ViewHolder getChildViewHolder(View view) { return getChildViewHolderInt(view); } @Override public void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams) { final ViewHolder vh = getChildViewHolderInt(child); if (vh != null) { if (!vh.isTmpDetached() && !vh.shouldIgnore()) { throw new IllegalArgumentException("Called attach on a child which is not" + " detached: " + vh + exceptionLabel()); } if (DEBUG) { Log.d(TAG, "reAttach " + vh); } vh.clearTmpDetachFlag(); } RecyclerView.this.attachViewToParent(child, index, layoutParams); } @Override public void detachViewFromParent(int offset) { final View view = getChildAt(offset); if (view != null) { final ViewHolder vh = getChildViewHolderInt(view); if (vh != null) { if (vh.isTmpDetached() && !vh.shouldIgnore()) { throw new IllegalArgumentException("called detach on an already" + " detached child " + vh + exceptionLabel()); } if (DEBUG) { Log.d(TAG, "tmpDetach " + vh); } vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); } } RecyclerView.this.detachViewFromParent(offset); } @Override public void onEnteredHiddenState(View child) { final ViewHolder vh = getChildViewHolderInt(child); if (vh != null) { vh.onEnteredHiddenState(RecyclerView.this); } } @Override public void onLeftHiddenState(View child) { final ViewHolder vh = getChildViewHolderInt(child); if (vh != null) { vh.onLeftHiddenState(RecyclerView.this); } } }); }
mAdapterHelper
void initAdapterManager() { mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { @Override public ViewHolder findViewHolder(int position) { final ViewHolder vh = findViewHolderForPosition(position, true); if (vh == null) { return null; } // ensure it is not hidden because for adapter helper, the only thing matter is that // LM thinks view is a child. if (mChildHelper.isHidden(vh.itemView)) { if (DEBUG) { Log.d(TAG, "assuming view holder cannot be find because it is hidden"); } return null; } return vh; } @Override public void offsetPositionsForRemovingInvisible(int start, int count) { offsetPositionRecordsForRemove(start, count, true); mItemsAddedOrRemoved = true; mState.mDeletedInvisibleItemCountSincePreviousLayout += count; } @Override public void offsetPositionsForRemovingLaidOutOrNewView( int positionStart, int itemCount) { offsetPositionRecordsForRemove(positionStart, itemCount, false); mItemsAddedOrRemoved = true; } @Override public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) { viewRangeUpdate(positionStart, itemCount, payload); mItemsChanged = true; } @Override public void onDispatchFirstPass(AdapterHelper.UpdateOp op) { dispatchUpdate(op); } void dispatchUpdate(AdapterHelper.UpdateOp op) { switch (op.cmd) { case AdapterHelper.UpdateOp.ADD: mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); break; case AdapterHelper.UpdateOp.REMOVE: mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); break; case AdapterHelper.UpdateOp.UPDATE: mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount, op.payload); break; case AdapterHelper.UpdateOp.MOVE: mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); break; } } @Override public void onDispatchSecondPass(AdapterHelper.UpdateOp op) { dispatchUpdate(op); } @Override public void offsetPositionsForAdd(int positionStart, int itemCount) { offsetPositionRecordsForInsert(positionStart, itemCount); mItemsAddedOrRemoved = true; } @Override public void offsetPositionsForMove(int from, int to) { offsetPositionRecordsForMove(from, to); // should we create mItemsMoved ? mItemsAddedOrRemoved = true; } }); }
LayoutManager
RecyclerView在经过
onFinishInflate()方法后,就完成了加载,还要经历
onAttachedToWindow()方法添加到
Window中。这样
Activity才能获取到
RecyclerView对象,并对它做一些设置。首先为
RecyclerView设置
LayoutManager,在
Activity中,通常进行如下设置
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
那么,我们就以这一段代码开始分析。
LinearLayoutManager
首先看看如何创建LinearLayoutManager对象
public LinearLayoutManager(Context context) { this(context, VERTICAL, false); } public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) { setOrientation(orientation); setReverseLayout(reverseLayout); setAutoMeasureEnabled(true); }
如果只用一个参数的构造方法,其实调用了三个参数的构造方法,也就是默认创建了垂直方向的
LinearLayoutManager。
因此,如果你想要创建水平方向的
LinearLayoutManager,你就需要调用三个参数的构造方法,把第二个参数设置为
LinearLayoutManager.HORIZONTAL。
最后一个参数
reverseLayout的意思自己试一下就直到了。
最终,这个构造函数只是初始化了三个变量
mOrientation为
VERTICAL
mReverseLayout为
false
mAutoMeasure为
true
setLayoutManager
创建了LayoutManager后,现在看看如何给
RecyclerView设置
public void setLayoutManager(LayoutManager layout) { // ... 省略无数行 mLayout = layout; if (layout != null) { if (layout.mRecyclerView != null) { throw new IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView:" + layout.mRecyclerView.exceptionLabel()); } mLayout.setRecyclerView(this); if (mIsAttached) { mLayout.dispatchAttachedToWindow(this); } } mRecycler.updateViewCacheSize(); requestLayout(); }
由于才刚刚设置
LayoutManager,因此我省略的不相干的无数行代码。
首先看下
mLayout.setRecyclerView(this)
void setRecyclerView(RecyclerView recyclerView) { if (recyclerView == null) { mRecyclerView = null; mChildHelper = null; mWidth = 0; mHeight = 0; } else { mRecyclerView = recyclerView; mChildHelper = recyclerView.mChildHelper; mWidth = recyclerView.getWidth(); mHeight = recyclerView.getHeight(); } mWidthMode = MeasureSpec.EXACTLY; mHeightMode = MeasureSpec.EXACTLY; }
很简单,这里为
LayoutManager对象初始化了
mRecyclerView,
mChildHelper,
mWidth,
mHeight,
mWidthMode,
mHeightMode成员变量。
再回到
setLayoutManager()方法的第14行,
mIsAttached变量在
onAttachedToWindow()方法中被设置为 true,所以看第15行
mLayout.dispatchAttachedToWindow(this)
// LayoutManager.java void dispatchAttachedToWindow(RecyclerView view) { mIsAttachedToWindow = true; onAttachedToWindow(view); }
这里就只是为
LayoutManager对象设置
mIsAttachedToWindow为
true,而
onAttachedToWindow()是个空方法。
setLayoutManager()最后调用了
requestLayout()方法进行重新布局。
ItemDecoration
addItemDecoration(),
setLayoutManager(),
setAdapter()的调用顺序其实没有关系,我这里把它提前,平常开发中,会选择添加分割线,代码如下
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL))
按照这个代码进行分析。
DividerItemDecoration
首先是创建ItemDecoration对象,这里创建的是
DividerItemDecoration对象,看构造方法
private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; public DividerItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); if (mDivider == null) { Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this " + "DividerItemDecoration. Please set that attribute all call setDrawable()"); } a.recycle(); setOrientation(orientation); }
老规矩,初始化变量
mDivider和
mOrientation。 其中
mDivider是系统提供的
drawable,
ListView的分隔线就是使用的这个
drawable。
从这段代码的 Log 信息中可以看到,可以通过调用
setDrawable()方法来设置自定义的分割线。
addItemDecoration
创建了ItemDecoration对象,现在就是设置
ItemDecoration了。
public void addItemDecoration(ItemDecoration decor) { addItemDecoration(decor, -1); } public void addItemDecoration(ItemDecoration decor, int index) { if (mLayout != null) { mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" + " layout"); } if (mItemDecorations.isEmpty()) { setWillNotDraw(false); } // 1. 保存 decor 到 mItemDecorations(ArrayList<ItemDecoration>类型) if (index < 0) { mItemDecorations.add(decor); } else { mItemDecorations.add(index, decor); } // 2. 标记 RecyclerView 的所有 children 和 ViewHolder.itemView 的 LayoutParams.mInsetsDirty 为 true markItemDecorInsetsDirty(); // 3. 请求重新布局 requestLayout(); }
例子中调用的是一个参数的构造方法,默认调用 了两个参数的构造方法,
index设置为了
-1。
纵观整个方法,分了三步,我已经用注释标明了。
最主要的就是第一步,由于
index为
-1调用的就是
mItemDecorations.add(decor)。
mItemDecorations是一个
ArrayList< ItemDecoration >类型的变量,也就是说,如果我们添加了多个
ItemDecoration,依次往
ArrayList中添加。 而如果我们想给自己的
ItemDecoration定义个顺序,那么就用两个参数的构造方法。
可见顺序很重要,因为影响了绘制的顺序,这个后面可以看到。
最后一步调用了
requestLayout()来请求重新布局。
Adapter
Android没有像
ListView一样,提供写好的一些
Adapter,需要自己继承
RecyclerView.Adapter。这里就不演示如何写
Adapter了,只分析过程。
public void setAdapter(Adapter adapter) { // bail out if layout is frozen setLayoutFrozen(false); setAdapterInternal(adapter, false, true); requestLayout(); }
setAdapter()方法在这里做的工作并不多,给变量
mAdapter赋值 ,为
mAdapter设置数据监听器。
到这里为止,好像并没有把加数加载进去。确实没有,因为我们需要再次刷新布局,这也就是为什么
setLayoutManager(),
setAdapter(),
addItemDecoration()方法最后都会调用
requestLayout()的原因。那么现在就进入了
measure,
layout,
draw过程。
onMeasure()
@Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); return; } // 在 LinearLayoutManager 的构造函数中,默认设置 mAutoMeasure 为 true if (mLayout.mAutoMeasure) { final int widthMode = MeasureSpec.getMode(widthSpec); final int heightMode = MeasureSpec.getMode(heightSpec); final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; // 实际也是调用 RecyclerView 的 defaultOnMeasure() 来进行测量,并保存测量的宽高 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); if (skipMeasure || mAdapter == null) { return; } // ...省略几千行 } // ...省略一万行 }
measure过程比较简单,代码已经注释了。 这里我只考虑了一类情形的测量,就是为
RecyclerView设置准确的值,如
300dp或者
MATCH_PARENT,这样就省略后面一大堆的步骤,~.~!
可以看到,如果
LayoutManager不为
null,会调用
LayoutManager的
onMeasure()方法为
RecyclerView进行 measure 的过程。这就是前面提到的,
LayoutManager的作用包括了为
RecyclerView进行测量。
LinearLayoutManger并没有复写
onMeasure()方法,所以调用了
LayoutManager的
onMeasure()方法,代码如下
// LayoutManager 的 onMeasure() 方法 public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); } // RecyclerView 的 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); }
可以看到最终测量的策略采用的是
LayoutManager的一个静态方法
chooseSize(),代码如下
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); } }
从这里可以看出,如果给
RecyclerView的宽/高设置了
WRAP_CONTENT属性,那就不能正常显示的,值为
Math.min(size, Math.max(desired, min))。
而如果设置为了
MATCH_PAREN或者类似
300dp这样的准确值,就可以得到精确的宽高值。
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; } void dispatchLayout() { if (mAdapter == null) { return; } if (mLayout == null) { return; } mState.mIsMeasuring = false; if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); // 给 LayoutManager 的 mWidth,mWidthMode,mHeight,mHeightMode 赋值 mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) { mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } else { mLayout.setExactMeasureSpecsFrom(this); } dispatchLayoutStep3(); }
到这里,就真正的开始布局了,分为了三步
dispatchLayoutStep1()
dispatchLayoutStep2()
dispatchLayoutStep3()
dispatchLayoutStep1()
/** * The first step of a layout where we; * - process adapter updates * - decide which animation should run * - save information about current views * - If necessary, run predictive layout and save its information */ private void dispatchLayoutStep1() { mState.assertLayoutStep(State.STEP_START); // 由于是刚开始布局,没有滑动,这里只是设置mState的mRemainingScrollHorizontal和mRemainingScrollVertical为0 fillRemainingScrollValues(mState); mState.mIsMeasuring = false; eatRequestLayout(); mViewInfoStore.clear(); onEnterLayoutOrScroll(); processAdapterUpdatesAndSetAnimationFlags(); saveFocusInfo(); mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged; mItemsAddedOrRemoved = mItemsChanged = false; // mState.mInPreLayout 为 false mState.mInPreLayout = mState.mRunPredictiveAnimations; // mState.mItemCount 等于 Adapter 中 getItemCount() 返回的个数 mState.mItemCount = mAdapter.getItemCount(); // RecyclerView 没有 children,所以mMinMaxLayoutPositions数组的两个值都是 NO_POSITION findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); if (mState.mRunSimpleAnimations) { // Step 0: Find out where all non-removed items are, pre-layout // 本文不分析动画 ,所以这里省略一万行 ... } if (mState.mRunPredictiveAnimations) { // Step 1: run prelayout: This will use the old positions of items. The layout manager // 本文不分析动画 ,所以这里省略一万行 ... } else { clearOldPositions(); } onExitLayoutOrScroll(); resumeRequestLayout(false); mState.mLayoutStep = State.STEP_LAYOUT; }
从注释中就可以看出,
dispatchLayoutStep1()做了四件事,而这四件事基本与本文的分析方向无关,这里只需要关心
mState几个成员变量的值。
mState.mInPreLayout为
false
mState.mItemCount为
mAdapter.getItemCount()
第二步之前还调用了
mLayout.setExactMeasureSpecsFrom(this)让
LayoutManager保存
RecyclerView的宽高的
size和
mode
void setExactMeasureSpecsFrom(RecyclerView recyclerView) { setMeasureSpecs( MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY) ); } void setMeasureSpecs(int wSpec, int hSpec) { mWidth = MeasureSpec.getSize(wSpec); mWidthMode = MeasureSpec.getMode(wSpec); if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { mWidth = 0; } mHeight = MeasureSpec.getSize(hSpec); mHeightMode = MeasureSpec.getMode(hSpec); if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { mHeight = 0; } }
这里就是为
LayoutManager对象设置了
mWidth,
mWidthMode,
mHeight,
mHeightMode。 还记得前面也设置过一次吗?这里为何还要再设置一次呢,因为调用过
onMeasure,所以得重新保存一次。
dispatchLayoutStep2()
第二步,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); // Step1: data update in one pass 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); }
dispatchLayoutStep2()做了两件事情
mAdapterHelper.consumeUpdatesInOnePass();对数据的操作(增加,删除,更新,移动)做更新。由于
RecyclerView还没有
children,所以也就无法操作。
mLayout.onLayoutChildren(mRecycler, mState);用
LayoutManager对
RecyclerView添加
View,并布局
以
LinearLayoutManager的
onLayoutChildren()方法为例
@Override 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. // ... // 第一步: 确定 mLayoutState 和 mOrientationHelper 已经被赋值 ensureLayoutState(); mLayoutState.mRecycle = false; // resolve layout direction // 决定 mShouldReverseLayout 值,因为我们设置的是垂直,所以值等于 mReverseLayout = false resolveShouldLayoutReverse(); final View focused = getFocusedChild(); if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION || mPendingSavedState != null) { mAnchorInfo.reset(); // 两个都是 false, 所以 mLayoutFromEnd 也是 false mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; //关键步骤: calculate anchor position and coordinate // 1. mAnchorInfo.mCoordinate = mRecyclerView.getPaddingTop() // 2. mAnchorInfo.mPosition = 0; updateAnchorInfoForLayout(recycler, state, mAnchorInfo); // 设置 mAnchroInfo.mValid 为 true mAnchorInfo.mValid = true; } // ... // LLM may decide to layout items for "extra" pixels to account for scrolling target, // caching or predictive animations. int extraForStart; int extraForEnd; // 还没有滚动位置,所以 extra 为 0 final int extra = getExtraLayoutSpace(state); // If the previous scroll delta was less than zero, the extra space should be laid out // at the start. Otherwise, it should be at the end. // extraForEnd 和 extraForStart 都为 0 if (mLayoutState.mLastScrollDelta >= 0) { extraForEnd = extra; extraForStart = 0; } else { extraForStart = extra; extraForEnd = 0; } // extraForStart 再加上 mRecyclerView.getPaddingTop() extraForStart += mOrientationHelper.getStartAfterPadding(); // extraForEnd 再加上 mRecyclerView.getPaddingBottom() extraForEnd += mOrientationHelper.getEndPadding(); // state.isPreLayout() 返回 false if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION && mPendingScrollPositionOffset != INVALID_OFFSET) { // ... } int startOffset; int endOffset; final int firstLayoutDirection; // mAnchorInfo.mLayoutFromEnd 为 false if (mAnchorInfo.mLayoutFromEnd) { firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL : LayoutState.ITEM_DIRECTION_HEAD; } else { firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : LayoutState.ITEM_DIRECTION_TAIL; } // LinearLayoutManager 中,为空方法 onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); // 还没有 children,不做处理 detachAndScrapAttachedViews(recycler); // resolveIsInfinite() 为 false mLayoutState.mInfinite = resolveIsInfinite(); mLayoutState.mIsPreLayout = state.isPreLayout(); // mAnchorInfo.mLayoutFromEnd 默认为 false if (mAnchorInfo.mLayoutFromEnd) { // ... } else { // fill towards end // 根据 mAnchorInfo 更新 mLayoutState updateLayoutStateToFillEnd(mAnchorInfo); // 前面说过 extraForEnd 等于额外的空间(这里为0)加上recyclerview的paddingBottom 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; } } // changes may cause gaps on the UI, try to fix them. // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have // changed if (getChildCount() > 0) { // because layout from end may be changed by scroll to position // we re-calculate it. // find which side we should check for gaps. if (mShouldReverseLayout ^ mStackFromEnd) { int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true); startOffset += fixOffset; endOffset += fixOffset; fixOffset = fixLayoutStartGap(startOffset, recycler, state, false); startOffset += fixOffset; endOffset += fixOffset; } else { int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true); startOffset += fixOffset; endOffset += fixOffset; fixOffset = fixLayoutEndGap(endOffset, recycler, state, false); startOffset += fixOffset; endOffset += fixOffset; } } layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); if (!state.isPreLayout()) { mOrientationHelper.onLayoutComplete(); } else { mAnchorInfo.reset(); } mLastStackFromEnd = mStackFromEnd; if (DEBUG) { validateChildOrder(); } }
这个方法内部写明了步骤,但是代码很长,那现在来一步步抽丝剥茧。
ensureLayoutState()是为了确保
mLayoutState和
mOrientationHelper被初始化。
void ensureLayoutState() { if (mLayoutState == null) { // 只是 new LayoutState() mLayoutState = createLayoutState(); } if (mOrientationHelper == null) { mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation); } }
OrientationHelper.createOrientationHelper()方法如下
public static OrientationHelper createOrientationHelper( RecyclerView.LayoutManager layoutManager, int orientation) { switch (orientation) { case HORIZONTAL: return createHorizontalHelper(layoutManager); case VERTICAL: return createVerticalHelper(layoutManager); } throw new IllegalArgumentException("invalid orientation"); }
很明显,调用的是
createVerticalHelper()方法,这个方法很长,但是我这里还是要把代码贴出来,方便后面查询。
public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) { return new OrientationHelper(layoutManager) { @Override public int getEndAfterPadding() { return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom(); } @Override public int getEnd() { return mLayoutManager.getHeight(); } @Override public void offsetChildren(int amount) { mLayoutManager.offsetChildrenVertical(amount); } @Override public int getStartAfterPadding() { return mLayoutManager.getPaddingTop(); } @Override public int getDecoratedMeasurement(View view) { final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin + params.bottomMargin; } @Override public int getDecoratedMeasurementInOther(View view) { final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin + params.rightMargin; } @Override public int getDecoratedEnd(View view) { final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin; } @Override public int getDecoratedStart(View view) { final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); return mLayoutManager.getDecoratedTop(view) - params.topMargin; } @Override public int getTransformedEndWithDecoration(View view) { mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); return mTmpRect.bottom; } @Override public int getTransformedStartWithDecoration(View view) { mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); return mTmpRect.top; } @Override public int getTotalSpace() { return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop() - mLayoutManager.getPaddingBottom(); } @Override public void offsetChild(View view, int offset) { view.offsetTopAndBottom(offset); } @Override public int getEndPadding() { return mLayoutManager.getPaddingBottom(); } @Override public int getMode() { return mLayoutManager.getHeightMode(); } @Override public int getModeInOther() { return mLayoutManager.getWidthMode(); } }; }
再接着看
LinearLayoutManager的
onLayoutChildren()方法中的
resolveShouldLayoutReverse()方法
private void resolveShouldLayoutReverse() { // A == B is the same result, but we rather keep it readable if (mOrientation == VERTICAL || !isLayoutRTL()) { mShouldReverseLayout = mReverseLayout; } else { mShouldReverseLayout = !mReverseLayout; } }
mReverseLayout是在
LinearLayoutManager的构造函数中初始化的,之前的分析结果是
false,所以
mShouldReverseLayout也为
false。
再接着看下
LinearLayoutManager的
onLayoutChildren()方法的下面代码,用来寻找绘制的锚点的信息 — 位置和坐标。
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; }
变量
mAnchorInfo在声明的时候就已经 new 了一个对象赋值给它了,所有的成员变量都是默认值。
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd这行代码中,
mShouldReverseLayout前面刚提过是false,而
mStackFromEnd默认是
false,所以
mAnchorInfo.mLayoutFromEnd是
false。
updateAnchorInfoForLayout(recycler, state, mAnchorInfo)用来计算锚点的位置和坐标,代码如下
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; }
前两个 if 语句,因为条件不够,并没有做任何事情,这里跳过分析。
anchorInfo.assignCoordinateFromPadding()这个是对 变量
mAnchroInfo的成员变量
mCorrdinate赋值
void assignCoordinateFromPadding() { mCoordinate = mLayoutFromEnd ? mOrientationHelper.getEndAfterPadding() : mOrientationHelper.getStartAfterPadding(); }
mLayoutFromEnd的值是
false,那么
mCoordinate的值实际就是
mOrientationHelper.getStartAfterPadding(), 实际就是调用
mLayoutManager.getPaddingTop(),最终调用的就是
mRecyclerView.getPaddingTop(),所以
anchorInfo.mCoordinate其实就是等于
mRecyclerView.getPaddingTop()。
而
anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;这一行,因为
mStackFromEnd为
false,导致
mAnchorInfo的
mPosition为
0。
现在再来看看
onLayoutChidren()的 下面代码
// LLM may decide to layout items for "extra" pixels to account for scrolling target, // caching or predictive animations. int extraForStart; int extraForEnd; final int extra = getExtraLayoutSpace(state); // If the previous scroll delta was less than zero, the extra space should be laid out // at the start. Otherwise, it should be at the end. if (mLayoutState.mLastScrollDelta >= 0) { extraForEnd = extra; extraForStart = 0; } else { extraForStart = extra; extraForEnd = 0; } extraForStart += mOrientationHelper.getStartAfterPadding(); extraForEnd += mOrientationHelper.getEndPadding();
由于我们现在的分析还没有涉及到滚动或者动画,因此
final int extra = getExtraLayoutSpace(state);的结果就是
extra为
0,
mLayoutState.mLastScrollDelta就是默认值 0,也就是第一个 if 语句中,
extraForEnd为
0,
extraStart为
0.
最后两行,还对
extraForStart和
extraForEnd进行计算,这里调用的就是
mOrientationHelper的方法,具体两个调用如下
// mOrientationHelper 的两个方法 @Override public int getStartAfterPadding() { return mLayoutManager.getPaddingTop(); } @Override public int getEndPadding() { return mLayoutManager.getPaddingBottom(); }
实际上都是调用
RecylerView的相应的方法,那么此时
extraForStart值就为
0 + mRecyclerView.getPaddingTop()
extraForEnd值就是
0 + mRecyclerView.getPaddingBottom()。
现在再来看下
LinearLayoutManager的
onLayoutChildren()的下面代码,确定
layout的方向
final int firstLayoutDirection; if (mAnchorInfo.mLayoutFromEnd) { firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL : LayoutState.ITEM_DIRECTION_HEAD; } else { firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : LayoutState.ITEM_DIRECTION_TAIL; }
mAnchroInfor的
mLayoutFromEnd的值是
false,而且
mShouldReverseLayout也是
false,所以
firstLayoutDirenction的值为
LayoutState.ITEM_DIRECTION_TAIL,也就是从头到尾的方向,也就是从手机屏幕上面到下面。
现在再来看下
LinearLayoutManager的
onLayoutChildren()的下面代码
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); detachAndScrapAttachedViews(recycler); mLayoutState.mInfinite = resolveIsInfinite(); mLayoutState.mIsPreLayout = state.isPreLayout(); if (mAnchorInfo.mLayoutFromEnd) { // fill towards start // ... 这次真的省略了一万行 } 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; } }
onAnchorReady()在
LinearLayoutManger中是一个空方法。
detachAndScrapAttachedViews(recycler)由于
RecyclerfView还没有添加
View,因此做了不什么动作。
mLayoutState.mInfinite = resolveIsInfinite();赋值为
false,这是与
mOrentationHelper变量的方法有关,大家可以自己去查下。
mLayoutState.mIsPreLayout = state.isPreLayout();赋值也为
false,在前面有说过。
那么现在进入到
else的循环体中,首先我们应该看的就是
updateLayoutStateToFillEnd(mAnchorInfo)更新
mLayoutState的状态,用来后面填充 view 使用
private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) { updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate); } private void updateLayoutStateToFillEnd(int itemPosition, int offset) { // RecyclerVie 除去上下padding后的高度 mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset; mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : LayoutState.ITEM_DIRECTION_TAIL; mLayoutState.mCurrentPosition = itemPosition; mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END; mLayoutState.mOffset = offset; mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; }
anchorInfo.mPostion值为
0,
anchorInfo.mCoordinate值为
mRecyclerView.getPaddingTop(),这两个值前面说过。
mLayoutState变量的赋值,我这里一起列举下
mLayoutState.Available的值为
RecyerlView.getHeight - RecyclerView.getPaddingTop() - RecyclerView.getPaddingBottom()
mLayoutState.mItemDirection的值为
LayoutState.ITEM_DIRECTION_TAIL
mLayoutState.mLayoutDirection的值为
LayoutState.LAYOUT_END
mLayoutState.mOffset值为
anchor.mCoordinate,也就是
RecyclerView.getPaddingTop()
mLayoutState.mScrollingOffset的值为
LayoutState.SCROLLING_OFFSET_NaN
在
updateLayoutStateToFillEnd()方法之后,还有
mLayoutState.mExtra的值为
extraForEnd,也就是前面说过的
RecyclerView.getPaddingBottom
现在继续看
onLayoutChildren()的
fill(recycler, mLayoutState, state, false),这就是真正的给
RecyclerView填充
View的地方。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { // max offset we should set is mFastScroll + available final int start = layoutState.mAvailable; // ... int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); // ... layoutChunk(recycler, state, layoutState, layoutChunkResult); if (VERBOSE_TRACING) { TraceCompat.endSection(); } if (layoutChunkResult.mFinished) { break; } layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; /** * Consume the available space if: * * layoutChunk did not request to be ignored * * OR we are laying out scrap children * * OR we are not doing pre-layout */ if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null || !state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; // we keep a separate remaining space because mAvailable is important for recycling remainingSpace -= layoutChunkResult.mConsumed; } // ... return start - layoutState.mAvailable; }
前面已经对
mLayoutState的各种变量进行了赋值,这里应该就不难了。不过,
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;这行代码,计算出来的 remainingSpace 的大小为
mRecyclerView.getHeight - mRecyclerView.getPaddingTop(),why? 看后面分析,这里先留个神。
看看
while循环的条件
layoutState.mInfinite是为
false
remainingSpace现在肯定大于
0,后面会逐步减少
layoutState.hasMore(state)根据
mCurrentPosition(当前为0)来判断 Adapter 中是否有足够的数据,代码如下
// LayoutState 的 hasMore() 方法 boolean hasMore(RecyclerView.State state) { return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount(); } // RecyclerView.State 的 getItemCount() 方法 public int getItemCount() { return mInPreLayout ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) : mItemCount; }
可以看到,
hasMore()方法的实际逻辑就是
mCurrentPosition >= 0 && mCurrentPosition < mAdapter.getItemCount()
ok,循环条件已经看完,直接进入
layoutChunk()方法
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { View view = layoutState.next(recycler); if (view == null) { if (DEBUG && layoutState.mScrapList == null) { throw new RuntimeException("received null view when unexpected"); } // if we are laying out views in scrap, this may return null which means there is // no more items to layout. result.mFinished = true; return; } LayoutParams params = (LayoutParams) view.getLayoutParams(); if (layoutState.mScrapList == null) { // layoutState.mLayoutDirection 为 LAYOUT_END if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addView(view); } else { // 添加 view 到 Recyclerview addView(view, 0); } } else { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addDisappearingView(view); } else { addDisappearingView(view, 0); } } measureChildWithMargins(view, 0, 0); // child.getMeasuredHeight() + mDecorInsets.top + mDecorInsets.bottom + mRecyclerView.topMargin + mRecyclerview.bottomMargin result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); // 计算 view 的 left, top, right, bottom // 把 child 的4个margin,ItemDecoration 的 4 个 margin 都计算在内 int left, top, right, bottom; if (mOrientation == VERTICAL) { if (isLayoutRTL()) { right = getWidth() - getPaddingRight(); left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); } else { left = getPaddingLeft(); right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); } if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { bottom = layoutState.mOffset; top = layoutState.mOffset - result.mConsumed; } else { top = layoutState.mOffset; bottom = layoutState.mOffset + result.mConsumed; } } else { top = getPaddingTop(); bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { right = layoutState.mOffset; left = layoutState.mOffset - result.mConsumed; } else { left = layoutState.mOffset; right = layoutState.mOffset + result.mConsumed; } } // We calculate everything with View's bounding box (which includes decor and margins) // To calculate correct layout position, we subtract margins. // 对 view 进行 layout,详细见下面 layoutDecoratedWithMargins(view, left, top, right, bottom); if (DEBUG) { Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)); } // Consume the available space if the view is not removed OR changed if (params.isItemRemoved() || params.isItemChanged()) { result.mIgnoreConsumed = true; } result.mFocusable = view.hasFocusable(); }
首先用
layoutState.next(recycler)获取下一个 View
View next(RecyclerView.Recycler recycler) { // STEP 1: 从 LinearLayoutManager 的 mScrapList 中获取 if (mScrapList != null) { return nextViewFromScrapList(); } // STEP2: 从 mRecycler 中获取 final View view = recycler.getViewForPosition(mCurrentPosition); // STEP3: 更新 mCurrentPosition mCurrentPosition += mItemDirection; return view; }
我把
next()方法分为了三步,前两步是获取下一个
View,第三步是根据
mItemDirection更新
mCurrentPosition.
先看看第三步,
mItemDirection之前已经赋值了,为
LayoutState.ITEM_DIRECTION_TAIL,而这个值是
1,所以
mCurrentPosition就 +1。 因为从头部往尾部绘制,+1 理所当然。
第一步是从
LinearLayoutManager的
List<RecyclerView.ViewHolder> mScrapList中获取,
mScrapList的默认值为
null,因此刚开始布局的话,应该是获取不到的。
第二步就是从
mRecycler中获取,这段代码很长,我精简了下代码,主要是看到获取的几个路径,本文主要内容不在于缓存机制,所以不做分析。
/** * Obtain a view initialized for the given position. * * This method should be used by {@link LayoutManager} implementations to obtain * views to represent data from an {@link Adapter}. * <p> * The Recycler may reuse a scrap or detached view from a shared pool if one is * available for the correct view type. If the adapter has not indicated that the * data at the given position has changed, the Recycler will attempt to hand back * a scrap view that was previously initialized for that data without rebinding. */ public View getViewForPosition(int position) { return getViewForPosition(position, false); } View getViewForPosition(int position, boolean dryRun) { return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; } /** * Attempts to get the ViewHolder for the given position, either from the Recycler scrap, * cache, the RecycledViewPool, or creating it directly. * <p> * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return * rather than constructing or binding a ViewHolder if it doesn't think it has time. * If a ViewHolder must be constructed and not enough time remains, null is returned. If a * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this. */ @Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { if (position < 0 || position >= mState.getItemCount()) { throw new IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount() + exceptionLabel()); } boolean fromScrapOrHiddenOrCache = false; ViewHolder holder = null; // 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null; } // 1) Find by position from scrap/hidden list/cache if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); // ... } if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " + "position " + position + "(offset:" + offsetPosition + ")." + "state:" + mState.getItemCount() + exceptionLabel()); } final int type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap/cache via stable ids, if exists if (mAdapter.hasStableIds()) { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); // ... } if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); // ... } if (holder == null) { // fallback to pool if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" + position + ") fetching from shared pool"); } holder = getRecycledViewPool().getRecycledView(type); // ... } if (holder == null) { long start = getNanoTime(); if (deadlineNs != FOREVER_NS && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { // abort - we have a deadline we can't meet return null; } holder = mAdapter.createViewHolder(RecyclerView.this, type); if (ALLOW_THREAD_GAP_WORK) { // only bother finding nested RV if prefetching RecyclerView innerView = findNestedRecyclerView(holder.itemView); if (innerView != null) { holder.mNestedRecyclerView = new WeakReference<>(innerView); } } long end = getNanoTime(); mRecyclerPool.factorInCreateTime(type, end - start); if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); } } } // ... boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { if (DEBUG && holder.isRemoved()) { throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder + exceptionLabel()); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound; return holder; }
代码注释我并没有省略,可能刚开始看很难理解,但是只要你分析完代码,再看注释,就会有种恍然大悟的感觉。
代码我做了精简,可以看到有好多个地方获取 ViewHolder。 由于本文分析的情况是刚开始加载,所以需要用 Adapter 来创建,也就是
holder = mAdapter.createViewHolder(RecyclerView.this, type);这一行代码。
public final VH createViewHolder(ViewGroup parent, int viewType) { TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; TraceCompat.endSection(); return holder; }
LayoutManager的
createViewHolder()方法只做了两件事
调用实现类的
onCreateViewHolder()方法
设置
holder.mItemViewType的值为参数
viewType
创建了
ViewHolder了后,就需要绑定,也就是
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);这行代码进行绑定,看下这个源码
private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition, int position, long deadlineNs) { // ... mAdapter.bindViewHolder(holder, offsetPosition); // ... return true; }
精简代码后,就清晰了吧,就是调用了
Adapter的
bindViewHolder()方法进行绑定。
public final void bindViewHolder(VH holder, int position) { holder.mPosition = position; if (hasStableIds()) { holder.mItemId = getItemId(position); } holder.setFlags(ViewHolder.FLAG_BOUND, ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); TraceCompat.beginSection(TRACE_BIND_VIEW_TAG); onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); holder.clearPayload(); final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); if (layoutParams instanceof RecyclerView.LayoutParams) { ((LayoutParams) layoutParams).mInsetsDirty = true; } TraceCompat.endSection(); }
LayoutManager的
bindViewHolder做了不少事情。
给
holer.mPosition赋值为参数
position。 如果要获取一个
View在
Adapter中的位置的时候,这个参数就起了重要作用。
如果
Adapter复写了
hasStableIds()方法,并返回了
true,就会给
holder.mItemId赋值为
getItemId()返回的值,而这个
getItemId()也可能要复写。
调用
holder.setFlag()给
ViewHolder设置标志位为
FLAG_BOUND
调用
onBindViewHolder()方法
给
ItemView的
LayoutParams参数的变量
mInsetsDirty值为 true。代表需要布局参数更新,需要重新测量。
绑定完了之后还没有完,需要给
ViewHolder的
itemView设置
LayoutParams参数。那么,再回到
tryGetViewHolderForPositionByDeadline()方法的的下面片段
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
由于在
Adapter的
onCrateViewHolder()方法中,用
LayoutInflater.from()创建
View的时候,会传入一个
root参数,也就是
onCreateViewHolder()中的
ViewGroup parent参数。 因此这里获取到的
ViewGroup.LayoutParams不为
null。
那么,就走到了
checkLayoutParams(lp)
@Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p); } public boolean checkLayoutParams(LayoutParams lp) { return lp != null; }
很显然,
checkLayoutParams(lp)返回
true,那么就执行了
rvLayoutParams = (LayoutParams) lp;这一行代码了。
再回到
layoutChunck()方法,此时通过
View view = layoutState.next(recycler);已经获取了一个
ViewHolder.itemView,接下来就是把这个
View添加到
RecyclerView中,代码片段如下
if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addView(view); } else { addView(view, 0); } }
由于此时的
layoutState.mLayoutDirection是
LayoutSate.LAYOUT_END,所以调用了
LayoutManager的
addView(view);方法,而这个方法最终是调用了
RecyclerView的
addView(child, index)方法,这里就不深究了。
LinearLayoutManager的
layoutChunck()现在获取了
View,又把这个
View添加到了
RecyclerView中,接下来,就要测量这个
View了,也就是
measureChildWithMargins(view, 0, 0);这行代码。
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); widthUsed += insets.left + insets.right; heightUsed += insets.top + insets.bottom; // 下面两个计算宽高的规格 final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, canScrollHorizontally()); final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, canScrollVertically()); if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { // 让 child 自己去测量 child.measure(widthSpec, heightSpec); } }
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);这段代码获取的是一个增加了
ItemDecoration的
Rect
Rect getItemDecorInsetsForChild(View child) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.mInsetsDirty) { return lp.mDecorInsets; } if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) { // changed/invalid items should not be updated until they are rebound. return lp.mDecorInsets; } final Rect insets = lp.mDecorInsets; insets.set(0, 0, 0, 0); final int decorCount = mItemDecorations.size(); for (int i = 0; i < decorCount; i++) { mTempRect.set(0, 0, 0, 0); mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); insets.left += mTempRect.left; insets.top += mTempRect.top; insets.right += mTempRect.right; insets.bottom += mTempRect.bottom; } lp.mInsetsDirty = false; return insets; }
可以看到创建的
insets刚开始把四个坐标都设置为了
0, 然后调用
ItemDecoration的
getItemOffsets()方法给
mTempRect填充值,最后会惊奇的发现,
insets把所有
mTempRect的四个坐标值分别累加起来了。 非常有意思! 那这到底什么意思呢?往下看
widthUsed += insets.left + insets.right; heightUsed += insets.top + insets.bottom;
这两行,计算了已经使用的宽度和高度。 纳尼!所以从
ItemDecoration返回的
mTempRect坐标累加的结果居然是已经使用的宽度和高度的意思!很明显,这就相当于一个
padding。所以添加到
RecyclerView中的
View需要考虑这个
padding,因为它们用来绘制
ItemDecoration。
计算完已经使用的宽高后,就需要来计算
View的宽高的
size和
mode了,也就是
measureChildWithMargins()中的如下代码
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, canScrollHorizontally()); final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, canScrollVertically());
由于
LinearLayoutManager中的
mOrientation为
VERTICAL,所以
canScrollVertically()为
true,而
canScrollHorizontally()为
false。
而测量的策略采用的是
LayoutManager中的
getChildMeasureSpec(),代码如下
public static int getChildMeasureSpec(int parentSize, int parentMode, int padding, int childDimension, boolean canScroll) { int size = Math.max(0, parentSize - padding); int resultSize = 0; int resultMode = 0; if (canScroll) { // 测量高度 if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { switch (parentMode) { case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: resultSize = size; resultMode = parentMode; break; case MeasureSpec.UNSPECIFIED: resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; break; } } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } } else { // 测量宽度 if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = parentMode; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) { resultMode = MeasureSpec.AT_MOST; } else { resultMode = MeasureSpec.UNSPECIFIED; } } } //noinspection WrongConstant return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
getChildMeasureSpec()的第三个参数,因为我们设置的方向是垂直滚动,所以在测量高度的时候,第三个参数是
true,而测量宽度的时候是
false。最后通过
MeasureSpec.makeMeasureSpec()生成一个
int值来存储
size和
mode。
完成了这些之后就需要让
child来完成测量,也就是
measureChildWithMargins()方法中的如下片段
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { child.measure(widthSpec, heightSpec); }
shouldMeasureChild()方法决定了是否需要测量,其中一个决定性的因素就是
child在 XML 设置的宽高和实际设置测量的宽高是否相符,不然就需要
child自己测量。代码逻辑如下:
boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { return child.isLayoutRequested() || !mMeasurementCacheEnabled || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width) || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height); } private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) { final int specMode = MeasureSpec.getMode(spec); final int specSize = MeasureSpec.getSize(spec); if (dimension > 0 && childSize != dimension) { return false; } switch (specMode) { case MeasureSpec.UNSPECIFIED: return true; case MeasureSpec.AT_MOST: return specSize >= childSize; case MeasureSpec.EXACTLY: return specSize == childSize; } return false; }
现在还没有对获取的
View进行
layout,因此
shouldMeasureChild()应该是返回
true,也就是需要测量。所以会调用
child.measure(widthSpec, heightSpec);来让
View完成测量。
View的
measure完成了,接下来就是
layout。
layoutChunck()方法首先执行
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);来完成高度的测量,包括
ItemDecoration提供的所谓的 “padding”,以及
View自身的
topMargin和
bottomMargin。
// mOrientationHelper public int getDecoratedMeasurement(View view) { final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin + params.bottomMargin; } // LayoutManager.java public int getDecoratedMeasuredHeight(View child) { final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; return child.getMeasuredHeight() + insets.top + insets.bottom; }
这段代码很简单,
result.mConsumed的值为
child.getMeasuredHeight() + insets.top + insets.bottom + params.topMargin + params.bottomMargin
然后计算
left,
top,
right,
bottom的值,代码片段如下
int left, top, right, bottom; if (mOrientation == VERTICAL) { if (isLayoutRTL()) { right = getWidth() - getPaddingRight(); left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); } else { // 大部分情况都是从左到右, left = getPaddingLeft(); right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); } if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { bottom = layoutState.mOffset; top = layoutState.mOffset - result.mConsumed; } else { // layoutState.mLayoutDirection 值为 LayoutState.LAYOUT_END top = layoutState.mOffset; bottom = layoutState.mOffset + result.mConsumed; } } // mOrientationHelper @Override public int getDecoratedMeasurementInOther(View view) { final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin + params.rightMargin; } // LayoutManager.java public int getDecoratedMeasuredWidth(View child) { final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; return child.getMeasuredWidth() + insets.left + insets.right; }
这段代码就不多做解释了, 计算后的
left,
top,
right,
bottom如下
left = mRecyclerView.getPaddingLeft()
right = left + child.getMeasuredWidth() + insets.left + insets.right + params.leftMargin + params.rightMargin
top = layoutState.mOffset,也就是 mRecyclerView.getPaddingTop()
bottom = mRecyclerView.getPaddingTop() + result.mConsumed。 result.mConsumed 刚才已经计算出来了。。
有了
left,
top,
right,
bottom后,
layoutChunck()方法就调用
layoutDecoratedWithMargins(view, left, top, right, bottom);来
layout这个
View
public void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Rect insets = lp.mDecorInsets; child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin, right - insets.right - lp.rightMargin, bottom - insets.bottom - lp.bottomMargin); }
这个就很直观了,不用解释了。
至此
layoutChunck()方法已经分析完毕。那么,现在总结下
layoutChunck()方法到底做了那些事情
1. get view
2. add view
3. measure view
4. layout view
这四步是不是看起来很舒服~
那么,现在再回到 fill() 的方法,我再做了精简
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { // max offset we should set is mFastScroll + available final int start = layoutState.mAvailable; // ... int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { // ... layoutChunk(recycler, state, layoutState, layoutChunkResult); // ... layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null || !state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; // we keep a separate remaining space because mAvailable is important for recycling remainingSpace -= layoutChunkResult.mConsumed; } // ... } // ... return start - layoutState.mAvailable; }
直接看
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;这行代码,
layoutState.mOffset的值是
mRecyclerView.getPaddingTop(),
layoutChunkResult.mConsumed的值是之前已经
measure和
layout过的
View的高度(包括
margin和
ItemDecoration的
padding),
layoutState.mLayoutDirection的值是
LaoutState.LAYOUT_END,也就是
1。那么
layoutState.mOffset的值就已经很明显了,下一个
View的
measure和
layout的起始位置,为下一次循环调用
layoutChunck()做准备。
然后,
layoutState.mAvailable要减去这个
layout过的
View的高度(包括
margin和
ItemDecoration的
padding),也就是
layoutState.mAvailable -= layoutChunkResult.mConsumed;.
再然后,
remainingSpace也要做一样的动作,也就是
remainingSpace -= layoutChunkResult.mConsumed;。
这一切准备完成后,就进行下一次的 while 循环了,直到
remainingSpace小于等于 0,或者
mCurrentPosition大于等于
mAdapter.getItemCount()。
从
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;这一行可以看出来,除去整个界面显示的 所有
View,还至少额外多加载了一个
View,当然前提是
Adapter的
getItemCount()返回的总数要足够。
第一个
View的分析就完毕了,然后通过
while循环来做相同的操作,直到结束。 这样就把所有的
View都添加到了
RecyclerView中。
注意
fill()方法的返回值
start - layoutState.mAvailable,实际上就是等于所有获取到的
View的高度。不过我们暂时还用不到。
至此,
fill()方法已经分析完毕。
现在再返回到
onLayoutChildren()方法,继续看下面片段代码
// 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; }
从注释中看有两步,现在已经分析完了 “fill towards end” 这一步的
fill()方法,接下来用
endOffset保存了
mLayoutState.mOffset的值,然后用
lastElement保存了
mLayoutState.mCurrentPosition的值。 最后还添加了一个
mLayoutState.mAvailable > 0的条件,这个条件是在
Adapter提供的数据不够的情况下才会成立,如果发生这种情况,就会执行
extraForStart += mLayoutState.mAvailable;
接下来就是 “fill towards start”,从底部往头部填充,这种情况并不适合现在分析的状况,所以这里就不分析了。
然后,
onLayoutChildren()剩下的代码与动画有关,这里就不分析了。
那么,至此,
onLayoutChildren()方法已经分析完毕。剩余的
dispatchLayoutStep2()和 后面的
dispatchLayoutStep3()方法就是与动画有关,这里也不分析了。
那么现在看 ReyclerView 的 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); } // ... }
draw()方法首先调用的是
super.draw(), 系统就会绘制一些背景,滚动条,等等。 然后系统会调用
onDraw()方法,RecyclerView 复写了
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()方法中,可以看到调用了 ItemDecoration 的
onDraw()方法进行绘制。
调用完
onDraw()方法后,就再回到
draw()方法,可以看到调用了 ItemDecoration 的
onDrawOver()方法。 由此可见,ItemDecoration 的绘制顺序是先调用
onDraw()方法,再调用
onDrawOver()方法。
总结
本文主要对RecyclerView第一次加载显示
View的过程进行的简要分析,其中穿插了
ItemDecoration的绘制。 但是并不包括动画,也不包括回收机制的分析。 那么,经历了这篇文章的分析,我们能做什么呢?可以绘制自己的
ItemDecoration! 在下一篇,我将会举例分析如何绘制
ItemDecoration。 OK,这篇文章就到此为止了,如果有任何问题,欢迎提出。
相关文章推荐
- Android RecyclerView源码分析
- RecyclerView.java源码动画分析,每个itemView的动画交给了 DefaultItemAnimator 这个类进行处理
- RecyclerView 源码分析
- RecyclerView源码分析
- 从RecyclerView、NestedScrollView源码分析嵌套滑动异常
- 从源码角度分析RecyclerView监听滑动到底部失效
- Android RecyclerView下拉刷新的实现和源码分析
- RecyclerView系列之二:从源码中分析为RecyclerView添加分割线
- RecyclerView滑动源码分析
- 灵活且强大的RecyclerViewAdapter源码分析
- 灵活且强大的RecyclerViewAdapter源码分析
- RecyclerView源码分析
- Android recyclerview源码分析(二)
- 灵活且强大的RecyclerViewAdapter源码分析
- 灵活且强大的RecyclerViewAdapter源码分析
- recyclerview万能适配器用法以及源码分析
- (二十八)RecyclerView ItemTouchHelper 源码分析以及拓展
- RecyclerView源码分析
- Android RecyclerView上拉加载更多的实现和源码分析
- [Android] RecyclerView源码分析