类名 | 功能 |
---|---|
RecyclerView.LayoutManager | 负责Item视图的布局的显示 |
RecyclerView.ItemDecoration | 绘制Item的分割样式 |
RecyclerView.ItemAnimator | 负责处理数据添加删除时的动画效果 |
RecyclerView.Adapter | 创建每一项Item视图 |
RecyclerView.ViewHolder | 承载Item视图的子布局 |
RecyclerView.Recycler | 处理View的缓存 |
RecyclerView 把子 View 的 measure 和 layout 委托给了 LayoutManager,我们可以通过自定义 LayoutManager 来自定义 RecyclerView 的子 View 排列规则。Google 官方给我提供了3个实现类,LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager.
下面我们根据源码一步步来看 RecyclerView 是怎么实现的。
1、setLayoutManager(RecyclerView.LayoutManager layout):
设置LayoutManager的方法
public void setLayoutManager(@Nullable RecyclerView.LayoutManager layout) {
if (layout != this.mLayout) {
//停止滚动
this.stopScroll();
if (this.mLayout != null) {
if (this.mItemAnimator != null) {
//结束动画
this.mItemAnimator.endAnimations();
}
//移除并回收视图
this.mLayout.removeAndRecycleAllViews(this.mRecycler);
//回收废弃视图
this.mLayout.removeAndRecycleScrapInt(this.mRecycler);
this.mRecycler.clear();
if (this.mIsAttached) {
this.mLayout.dispatchDetachedFromWindow(this, this.mRecycler);
}
this.mLayout.setRecyclerView((RecyclerView)null);
this.mLayout = null;
} else {
this.mRecycler.clear();
}
this.mChildHelper.removeAllViewsUnfiltered();
//更新layout
this.mLayout = layout;
...
//更新缓存的View数量
this.mRecycler.updateViewCacheSize();
//请求measure layout draw
this.requestLayout();
}
}
2、setAdapter(RecyclerView.Adapter adapter)\
setAdapterInternal(RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews):
设置Adapter的方法
public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
this.setLayoutFrozen(false);
this.setAdapterInternal(adapter, false, true);
this.processDataSetCompletelyChanged(false);
this.requestLayout();
}
private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
...
this.mAdapter = adapter;
...
}
setAdapter()方法中调用了setAdapterInternal()方法,在setAdapterInternal()方法中初始化,然后调用 requestLayout()方法。
public void requestLayout() {
if (this.mInterceptRequestLayoutDepth == 0 && !this.mLayoutFrozen) {
super.requestLayout();
} else {
this.mLayoutWasDefered = true;
}
}
requestLayout()方法其实调用的是View的requestLayout()方法。
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
...
}
其中mParent.requestLayout()方法调用的是ViewRootImpl的requestLayout()方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals()方法被执行之后会走RecyclerView的onMeasure()、onLayout()、onDraw()等绘制方法。
3、onMeasure()、onLayout()
先来看一下onMeasure()方法的源码:
protected void onMeasure(int widthSpec, int heightSpec) {
...
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
//当 RecyclerView 当 Mode 是 EXACTLY 的时候大小是确定的,所以 return 退出 measure
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
//记录 layout 之前,view 的信息
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
...
}
}
}
mAutoMeasure的值是用来标记是否自动测量的,否则就必须在 LayoutManager 中自己实现 onMeasure 来进行测量,其中Google提共的三个LayoutManager实现类的mAutoMeasure返回值都是true。
public boolean isAutoMeasureEnabled() {
return true;
}
所以我们看 else 中的代码就可以了,首先如果RecyclerView 的 Mode 是 EXACTLY 的时候大小是确定的,所以就 return 退出 measure。然后会调用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
布局的第一步,进程适配器更新,决定应运行哪个动画,保存有关当前 View 的信息,如果有必要,运行 predictive layout。然后调用了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() {
...
this.mLayout.onLayoutChildren(this.mRecycler, this.mState);
...
mState.mLayoutStep = State.STEP_ANIMATIONS;
...
}
其中都核心代码就是调用了mLayout的onLayoutChildren()方法,这里的 mLayout 就是RecyclerView.LayourManager对象,用来规定放置子 view 的算法,寻找锚点填充 view。所以到这里我们可以知道RecyclerView的measure过程委托给了RecyclerView.LayourManager的onLayoutChildren()方法来处理。
再看看onLayout()方法:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
...
this.dispatchLayout();
...
}
其中 dispatchLayout() 方法的源码如下:
void dispatchLayout() {
...
this.mState.mIsMeasuring = false;
if (this.mState.mLayoutStep == State.STEP_START) {
this.dispatchLayoutStep1();
this.mLayout.setExactMeasureSpecsFrom(this);
this.dispatchLayoutStep2();
} else if (!this.mAdapterHelper.hasUpdates() && this.mLayout.getWidth() == this.getWidth() && this.mLayout.getHeight() == this.getHeight()) {
this.mLayout.setExactMeasureSpecsFrom(this);
} else {
this.mLayout.setExactMeasureSpecsFrom(this);
this.dispatchLayoutStep2();
}
//保存 view 动画的信息,执行动画,状态清理。
this.dispatchLayoutStep3();
}
dispatchLayout()里面调用了 dispatchLayoutStep1()、dispatchLayoutStep2()、dispatchLayoutStep3()方法,前两个方法的作用我们之前已经说过,现在来看看 dispatchLayoutStep3() 方法,源码太长看一下注释:
/**
* The final step of the layout where we save the information about views for animations,
* trigger animations and do any necessary cleanup.
*/
private void dispatchLayoutStep3() {
...
mState.mLayoutStep = State.STEP_START;
...
}
所以它的功能是保存 view 动画的信息,执行动画,状态清理。
这里有个重要点就是状态类 State,它是标记 layout 过程进行到哪一步了。在onMeasure()中当RecyclerView当Mode不是EXACTLY的时候,会执行dispatchLayoutStep1()、dispatchLayoutStep2()方法来测量子View的宽高来确定 RecyclerView自己的宽高,在onlayout()中就不需要执行了。当RecyclerView当Mode是EXACTLY的时候的时候不会执行dispatchLayoutStep1()、dispatchLayoutStep2()方法,在onlayout()中就需要执行dispatchLayoutStep1()、dispatchLayoutStep2()方法。
所以 RecyclerView的layout过程也委托给了RecyclerView.LayourManager的onLayoutChildren()方法来处理。
那么下面以LinearLayoutmanager为例:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
...
final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
...
int startOffset;
int endOffset;
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;
}
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
// start could not consume all it should. add more items towards end
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
...
}
根据方法中的注释可以知道,首先确定锚点mAnchorInfo,mAnchorInfo包含了子控件在Y轴上起始绘制偏移量(coordinate),ItemView在Adapter中的索引位置(position)和布局方向(mLayoutFromEnd)——这里是指start、end方向
static class AnchorInfo {
OrientationHelper mOrientationHelper;
int mPosition;
int mCoordinate;
boolean mLayoutFromEnd;
boolean mValid;
...
}
然后以锚点为起点向着开始和结束的方向填充ItemView。其中真正的填充方法是 fill() 方法:
/**
* The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
* independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
* and with little change, can be made publicly available as a helper class.
*/
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
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;
/**
* 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;
}
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
...
}
...
return start - layoutState.mAvailable;
}
由上面的伪代码可知,fill() 方法通过while循环调用 layoutChunk() 方法来填充子 View。下面看看 layoutChunk()方法:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
...
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
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.
layoutDecoratedWithMargins(view, left, top, right, bottom);
...
result.mFocusable = view.hasFocusable();
}
其中 View 通过 next()方法取出:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
在 next()方法中通过 recycler.getViewForPosition(mCurrentPosition) 方法获取的 View,这里是RecyclerView的缓存机制来,先不说。回到上面layoutChunk()方法,取到View之后通过addView()方法添加。