RecyclerView 源码分析(一)
来源:互联网 发布:网络安全产品分类 编辑:程序博客网 时间:2024/06/06 02:30
- 构造函数
- 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,这篇文章就到此为止了,如果有任何问题,欢迎提出。
- RecyclerView 源码分析(一)
- Android recyclerview源码分析(一)
- RecyclerView源码分析
- RecyclerView源码分析
- RecyclerView滑动源码分析
- Android RecyclerView源码分析
- RecyclerView源码分析
- RecyclerView源码分析
- RecyclerView源码分析
- RecyclerView源码分析
- RecyclerView 源码分析
- RecyclerView源码分析
- RecyclerView源码分析
- RecyclerView 源码分析
- Android中的RecyclerView源码分析
- RecyclerView ItemTouchHelper源码分析扩展
- RecyclerView.ItemAnimator终极解读(一)--RecyclerView源码解析
- Android recyclerview源码分析(二)
- canvas画太极
- python中get_dummies实践
- 内网Windos服务器VM虚拟机上(ContOs6.8)上安装mysql遇到的问题
- 6.14
- 欢迎使用CSDN-markdown编辑器
- RecyclerView 源码分析(一)
- POJ1061---青蛙的约会(同余方程,拓展gcd)
- 冒泡排序
- Android自定义View时获取文字宽高
- SSH(二)——实现DAO层和部署DAO层
- 游戏中的人工智能(一)
- CSS、JS 放置位置与前端性能的关系?
- 交换两个数
- 深入探究C++的new/delete操作符