您的位置:首页 > 其它

RecyclerView 源码分析(一)

2017-11-16 20:36 1561 查看
构造函数
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,这篇文章就到此为止了,如果有任何问题,欢迎提出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码分析