RecyclerView源码解析

本文详细解析了Android RecyclerView的源码,包括自身测量、复用机制和滑动处理。首先介绍了RecyclerView在没有设置LayoutManager时的默认测量策略,接着分析了开启自动测量时的布局流程,特别是如何根据子View的大小和布局调整自身大小。接着,文章讨论了复用机制,包括Scrap、CacheView和RecycledViewPool三级缓存,以及它们如何在数据更新和滑动时提高性能。最后,文章阐述了滑动处理,讲解了LayoutState的重要性和滑动后数据处理的流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • Adapter:Adapter的基类。负责将ViewHolder中的数据和RecyclerView中的控件进行绑定处理。

  • ViewHolder:视图和元数据类。它持有了要显示的数据信息,包括位置、View、ViewType等。

源码

无论是View还是ViewGroup的子类,都是通过 onMeasure() 来实现测量工作的,那么我们对于RecyclerView的源码解析就把onMeasure当作我们的切入点

自身测量

//RecyclerView.java

protected void onMeasure(int widthSpec, int heightSpec) {

//dispatchLayoutStep1,dispatchLayoutStep2,dispatchLayoutStep3肯定会执行,但是会根据具体的情况来区分是在onMeasure还是onLayout中执行。

if (mLayout == null) {//LayoutManager为空,那么就使用默认的测量策略

defaultOnMeasure(widthSpec, heightSpec);

return;

}

if (mLayout.mAutoMeasure) {

//有LayoutManager,开启了自动测量

final int widthMode = MeasureSpec.getMode(widthSpec);

final int heightMode = MeasureSpec.getMode(heightSpec);

final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;

//步骤1 调用LayoutManager的onMeasure方法来进行测量工作

mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

//如果width和height都已经是精确值,那么就不用再根据内容进行测量,后面步骤不再处理

if (skipMeasure || mAdapter == null) {

return;

}

//如果测量过程后的宽或者高都没有精确,那么就需要根据child来进行布局,从而来确定其宽和高。

// 当前的布局状态是start

if (mState.mLayoutStep == State.STEP_START) {

//布局的第一部 主要进行一些初始化的工作

dispatchLayoutStep1();

}

mLayout.setMeasureSpecs(widthSpec, heightSpec);

mState.mIsMeasuring = true;

//执行布局第二步。先确认子View的大小与布局

dispatchLayoutStep2();

// 布局过程结束,根据Children中的边界信息计算并设置RecyclerView长宽的测量值

mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

//检查是否需要再此测量。如果RecyclerView仍然有非精确的宽和高,或者这里还有至少一个Child还有非精确的宽和高,我们就需要再次测量。

// 比如父子尺寸属性互相依赖的情况,要改变参数重新进行一次

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 {

//有LayoutManager,没有开启自动测量。一般系统的三个LayoutManager都是自动测量,

// 如果是我们自定义的LayoutManager的话,可以通过setAutoMeasureEnabled关闭自动测量功能

//RecyclerView已经设置了固定的Size,直接使用固定值即可

if (mHasFixedSize) {

mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

return;

}

//如果在测量过程中数据发生变化,需要先对数据进行处理

// 处理完新更新的数据,然后执行自定义测量操作。

if (mAdapter != null) {

mState.mItemCount = mAdapter.getItemCount();

} else {

mState.mItemCount = 0;

}

eatRequestLayout();

//没有设置固定的宽高,则需要进行测量

mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

resumeRequestLayout(false);

mState.mInPreLayout = false;

}

}

在这个方法里面,根据不同的情况进行了不同的处理。

  1. 没有设置LayoutManager的情况下,直接使用默认的测量方法。

  2. 当设置了Layoutmanager而且开启了自动测量功能。

  3. 设置了LayoutManager但是没有开启自动测量。

我们先从最简单的来分析。

第一种:没有设置LayoutManager。

因为RecyclerView的所有的测量和布局工作都是交给LayoutManager来处理的,如果没有设置的话,只能使用默认的测量方案了。

第三种:有LayoutManager,而且关闭了自动测量功能。

关闭测量的情况下不需要考虑子View的大小和布局。直接按照正常的流程来进行测量即可。如果直接已经设置了固定的宽高,那么直接使用固定值即可。如果没有设置固定宽高,那么就按照正常的控件一样,根据父级的要求与自身的属性进行测量。

第二种:有LayoutManager,开启了自动测量。

这种情况是最复杂的,需要根据子View的布局来调整自身的大小。需要知道子View的大小和布局。所以RecyclerView将布局的过程提前到这里来进行了。

我们简化一下代码再看

//RecyclerView.java

if (mLayout.mAutoMeasure) {

//调用LayoutManager的onMeasure方法来进行测量工作

mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

//如果width和height都已经是精确值,那么就不用再根据内容进行测量,后面步骤不再处理

if (skipMeasure || mAdapter == null) {

return;

}

if (mState.mLayoutStep == State.STEP_START) {

//布局的第一部 主要进行一些初始化的工作

dispatchLayoutStep1();

}

//开启了自动测量,需要先确认子View的大小与布局

dispatchLayoutStep2();

//再根据子View的情况决定自身的大小

mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

if (mLayout.shouldMeasureTwice()) {

//如果有父子尺寸属性互相依赖的情况,要改变参数重新进行一次

dispatchLayoutStep2();

mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

}

}

对于RecyclerView的测量和绘制工作,是需要 dispatchLayoutStep1 ,dispatchLayoutStep2 , dispatchLayoutStep3 这三步来执行的,step1里是进行预布局,主要跟记录数据更新时需要进行的动画所需的信息有关,step2就是实际循环执行了子View的测量布局的一步,而step3主要是用来实际执行动画。而且通过 mLayoutStep记录了当前执行到了哪一步。在开启自动测量的情况下如果没有设置固定宽高,那么会执行setp1和step2。在step2执行完后就可以调用setMeasuredDimensionFromChildren 方法,根据子类的测量布局结果来设置自身的大小。

我们先不进行分析step1,step2和step3的具体功能。直接把 onLayout 的代码也贴出来,看一下这3步是如何保证都能够执行的。

//RecyclerView.java

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

dispatchLayout();

}

void dispatchLayout() {

if (mAdapter == null) {//没有设置adapter,返回

Log.e(TAG, “No adapter attached; skipping layout”);

// leave the state in START

return;

}

if (mLayout == null) {//没有设置LayoutManager,返回

Log.e(TAG, “No layout manager attached; skipping layout”);

// leave the state in START

return;

}

mState.mIsMeasuring = false;

//在onMeasure阶段,如果宽高是固定的,那么mLayoutStep == State.STEP_START 而且dispatchLayoutStep1和dispatchLayoutStep2不会调用

//所以这里就会调用一下

if (mState.mLayoutStep == State.STEP_START) {

dispatchLayoutStep1();

mLayout.setExactMeasureSpecsFrom(this);

dispatchLayoutStep2();

} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()|| mLayout.getHeight() != getHeight()) {

//在onMeasure阶段,如果执行了dispatchLayoutStep1,但是没有执行dispatchLayoutStep2,就会执行dispatchLayoutStep2

mLayout.setExactMeasureSpecsFrom(this);

dispatchLayoutStep2();

} else { <

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值