前言
本文打算对 RecyclerView 做一个详细完整的、重点突出的分析与总结,因为 RecycelrView 源码很长(光 RecyclerView 文件本身就有 13000+ 行),因此文章也会很长,但一通分析下来后会发现,RecyclerView 虽然是 ListView 的加强版,除了在使用方法上类似之外,关键源码上也是非常类似的。
RecyclerView 的使用可以参考大神的文章:
Android RecyclerView 使用完全解析 体验艺术般的控件
RecyclerView 和 ListView 使用对比分析
本文采用“自顶向下”的源码分析法——即把相关的代码按照调用关系自顶向下排列,同一个类的方法放在一起,关键的代码使用注释标注。废话不多说,下面从 RecyclerView 的绘制流程开始分析。
PS:CSDN 的代码编辑器实在不知道怎么设置,觉得丑的可以到简书或掘金上看
绘制流程
先从 onMeasure 方法看起,关键的地方都用注释标出来了:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
Adapter mAdapter;
@VisibleForTesting LayoutManager mLayout;
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) { // 看这里就行了
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
// 关键步骤 1
// 默认为 start
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// 关键步骤 2
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()) {
...
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
...
} else if (mState.mRunPredictiveAnimations) {
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
return;
}
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
...
}
}
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);
}
public abstract static class LayoutManager {
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
}
}
mLayout.isAutoMeasureEnabled() 的返回值默认为 false,但 LinearLayoutManager 和 StaggeredGridLayoutManager 重写了这个方法,且默认为 true:
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
@Override
public boolean isAutoMeasureEnabled() {
return true;
}
}
public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements
RecyclerView.SmoothScroller.ScrollVectorProvider {
public static final int GAP_HANDLING_NONE = 0;
public static final int GAP_HANDLING_LAZY = 1;
public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
public void setGapStrategy(int gapStrategy) {
...
mGapStrategy = gapStrategy;
requestLayout();
}
@Override
public boolean isAutoMeasureEnabled() {
return mGapStrategy != GAP_HANDLING_NONE;
}
}
而 GridLayoutManager 继承自 LinearLayoutManager,即 Android 提供的 LayoutManager 的三个子类,都会走 if 分支里的代码,里面最重要的两个调用是 dispatchLayoutStep1 和 dispatchLayoutStep2,在开始分析这两个方法之前,先看一下 onLayout 方法:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
dispatchLayout();
mFirstLayoutComplete = true;
}
/**
* Wrapper around layoutChildren() that handles animating changes caused by layout.
*/
void dispatchLayout() {
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
// onMeasure 已经执行了 step1 和 step2,正常情况下不会走这个分支
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// adpter 更新了,或者 width 或 height 改变了,重新走一遍 step2
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// 设置 MeasureSpec 为 EXACTLY
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
// 关键步骤 3
dispatchLayoutStep3();
}
public abstract static class LayoutManager {
void setExactMeasureSpecsFrom(RecyclerView recyclerView) {
setMeasureSpecs(
MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)
);
}
}
}
可以发现,RecyclerView 的 layout 流程分为三步,关键调用分别为 dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3,其中 onMeasure 方法执行前两部,onLayout 方法执行最后一步,下面开始分析这三个方法。
dispatchLayoutStep
dispatchLayoutStep1
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
/**
* 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() {
...
processAdapterUpdatesAndSetAnimationFlags();
if (mState.mRunSimpleAnimations) {
// Step 0: 找出所有未删除的 item 的位置, 准备执行预布局
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(...);
mViewInfoStore.addToPreLayout(holder, animationInfo);
...
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: 运行预布局
// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// 借助 LayoutManager 完成 layout 流程
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
...
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(...);
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
mState.mLayoutStep = State.STEP_LAYOUT;
}
private void processAdapterUpdatesAndSetAnimationFlags() {
// 默认为 false,第一次 measure 时不会执行
if (mDataSetHasChangedAfterLayout) {
mAdapterHelper.reset();
if (mDispatchItemsChangedEvent) {
mLayout.onItemsChanged(this);
}
}
// 两个分支里的代码最后调用的都是 AdapterHelper 内部的接口 Callback 的方法
// Callback 的实现可以查看 RecyclerView 的 initAdapterManager 方法
// 最后可能会调用 requestLayout 走一遍绘制流程以实现动画的效果
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
mState.mRunSimpleAnimations = mFirstLayoutComplete && ...;
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
static ViewHolder getChildViewHolderInt(View child) {
if (child == null) {
return null;
}
return ((LayoutParams) child.getLayoutParams()).mViewHolder;
}
}
根据 dispatchLayoutStep1 的注释及代码,可以得知这个方法主要负责:
- 执行 adapter 更新,最终可能会调用 requestLayout
- 决定哪些动画应该被执行(暂时不会被执行)
- 保存子 View 的相关信息
- 如果有必要,执行预布局
而如果是第一次执行 measure 流程,adapter 正常来说是没有可以更新的元素的,即此时 dispatchLayoutStep1 的作用主要是计算并保存子 View 和动画的相关信息。
dispatchLayoutStep2
预布局是否执行涉及的变量比较多,先忽略。现在继续看 dispatchLayoutStep2:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
/**
* 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() {
...
// 借助 LayoutManager 完成实际上的布局
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
...
}
}
可以看到,相比 dispatchLayoutStep1,dispatchLayoutStep2 简单得多,作用是执行真正的布局,调用的方法是 mLayout.onLayoutChildren,这个方法在 LayoutManager 中是一个空实现,它的三个子类 LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager 都有自己的实现。下面以 LinearLayoutManager 为例(关键部分,比如缓存机制,三个 LayoutManager 都是一样的):
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
@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.
// create layout state
if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
}
...
// 寻找子 View 的锚点坐标及位置
final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION || mPendingSavedState != null) {
updateAnchorInfoForLayout