RecyclerView使用的广泛程度,相信大家都深有体会。作为android的一个大杀器,本身的代码量也是很可观的。本着整理学习的心态,于是就开始了本次的RecyclerView的源码分析之旅。
先说明一下,本系列使用的源码是androidx.recyclerview1.1.0。
本质上来说,所有的view控件解决的都是两个问题,一是绘制,二是交互。RecyclerView也不例外。所以,我们可以按照这个脉络去拆解RecyclerView。在这个系列文章中,我准备按照绘制,交互两条线去梳理RecyclerView的整个脉络。
本文是绘制的开篇,先梳理绘制的整体框架,后面将有系列文章来分析具体实现。
绘制,这里指的是measure, layout, draw。
众所周知,measure的目的是递归测量view的长宽,即计算当前view及子view的measureWidth/measureHeight。那么,RecyclerView是怎么实现的呢?
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
...
return;
}
if (mLayout.isAutoMeasureEnabled()) {
...
} else {
...
}
}
RecyclerView的onMeasure的代码结构很清晰,分三种情况:
一,mLayout==null
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
这种情况是LayoutManager对象为空的时候。
RecyclerView并没有提供默认的LayoutManager对象,当没有设置LayoutManager对象,也就是mLayout是null时,调用defaultOnMeasure()方法。
/**
* An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios
* where this RecyclerView is otherwise lacking better information.
*/
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);
}
defaultOnMeasure的作用注释说得很清楚,就是保底。看到了setMeasuredDimension,就知道是在这里设置的measuredWidth/measuredHeight。
二,mLayout.isAutoMeasureEnabled()为true
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
...
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()) {
...
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
}
这是LayoutManager支持自动测量的情况,是RecyclerView最常见的情况。
官方提供的LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager都是支持自动测量的。
在这种情况下,先调用LayoutManager的onMeasure方法。系统提供的LayoutManager都没有重写该方法,如果使用的是系统LayoutManager,最后都会变相调用defaultOnMeasure。
接着判断如果父控件提供的长宽mode都是EXACTLY,也就是RecyclerView的测量长宽都是确定的数值,这种情况下,不需要再考虑子view的的长宽对RecyclerView的影响,直接返回;如果Adapter为空,意味着没有子view,也直接返回。在这两种情况下,之前defaultOnMeasure中测量的RecyclerView的长宽就视为是最终的measuredWidth/measuredHeight。
接下来处理的就是父控件提供的长宽mode中有AT_MOST的情况,也就是考虑子view对RecyclerView长宽的影响。根据mState.mLayoutStep的状态是否是State.STEP_START来判断是否执行dispatchLayoutStep1(),接着执行dispatchLayoutStep2(),然后会调用LayoutManager的setMeasuredDimensionFromChildren(),代码如下,遍历当前屏幕上所有的可见子view,获取包含所有可见子view的区域rect的坐标。方法的最后一行,发现了setMeasuredDimension,和View中的setMeasuredDimension的参数不一样,多了一个Rect对象,这个是具体的设置measuredWidth/measuredHeight的地方,由具体的LayoutManager类实现,最终都会调用View的setMeasuredDimension,设置RecyclerView的measuredWidth/measuredHeight。RecyclerView提供了默认的实现方法,LinearLayoutManager使用的就是默认方法,GridViewLayoutManager,StaggeredGridLayoutManager重写了该方法。后续篇章会详细介绍到。
void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) {
final int count = getChildCount();
if (count == 0) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
return;
}
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
final Rect bounds = mRecyclerView.mTempRect;
getDecoratedBoundsWithMargins(child, bounds);
if (bounds.left < minX) {
minX = bounds.left;
}
if (bounds.right > maxX) {
maxX = bounds.right;
}
if (bounds.top < minY) {
minY = bounds.top;
}
if (bounds.bottom > maxY) {
maxY = bounds.bottom;
}
}
mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);
setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec);
}
最后判断是否需要二次测量,shouldMeasureTwice()是否为true。系统默认是false,LinearLayoutManager重写了这个方法,见下图,要求RecyclerVIew的宽高mode都必须是AT_MOST,也就是宽高都必须是wrap_content,或者宽高是match_parent,但是RecyclerView的父控件是wrap_content。同时RecyclerView的子view是match_paren/wrap_content,不能是具体的数值。在条件都满足的情况下,进行二次测量,也就是再走一遍dispatchLayoutStep2()和setMeasuredDimensionFromChildren()。
@Override
boolean shouldMeasureTwice() {
return getHeightMode() != View.MeasureSpec.EXACTLY
&& getWidthMode() != View.MeasureSpec.EXACTLY
&& hasFlexibleChildInBothOrientations();
}
boolean hasFlexibleChildInBothOrientations() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final ViewGroup.LayoutParams lp = child.getLayoutParams();
if (lp.width < 0 && lp.height < 0) {
return true;
}
}
return false;
}
三,mLayout.isAutoMeasureEnabled()为false
else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
...
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
...
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
...
}
这是RecyclerView不支持自动测量的情况。
如果mHasFixedSize为true,即RecyclerView的长宽不随Adapter content变动,调用LayoutManager的onMeasure方法,最终变相调用defaultOnMeasure,返回。
如果mHasFixedSize为false,就需要开发者自己定制LayoutManager,自己实现onMeasure进行measureWidth/measureHeight的测量了。我们主要用到的是第二种情况,所以这里不过多开展了。
通过对measure源码的分析,了解了RecyclerView在测量阶段做了哪些工作。但是也产生了以下疑问:RecyclerView的子view是在哪里测量的呢?dispatchLayoutStep1()和dispatchLayoutStep2()看着像是布局用的方法,放在测量里是干什么的?
并且dispatchLayoutStep1()和dispatchLayoutStep2()方法在布局阶段也用到了,我们一起看看它们做了什么事情。
RecyclerView的onLayout方法实现的很简洁,只有一个dispatchLayout()方法,且dispatchLayout()方法主要有三个方法dispatchLayoutStep1(),dispatchLayoutStep2(),dispatchLayoutStep3()。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
...
dispatchLayout();
...
}
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
...
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
}
...
dispatchLayoutStep3();
}
dispatchLayoutStep1记录的一些状态的变化,主要和动画相关;dispatchLayoutStep3主要做了做动画处理并且重置清理一些状态;这两个方法是对应的。实际的测量和布局工作是放在dispatchLayoutStep2中进行的。dispatchLayoutStep2的核心代码是onLayoutChildren(),RecyclerView的子view的测量和布局工作都是在这里处理的,LayoutManager中默认是空方法,系统提供的LayoutManager都实现了该方法,后续篇章会分析它们是如何测量和布局的子view。
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() {
...
// Step 2: Run layout
...
mLayout.onLayoutChildren(mRecycler, mState);
...
}
到此,我们看到了RecyclerView里的测量和布局只是搭了个架子,具体的实现是放在一个个具体的LayoutManager中的。想要了解具体怎么处理的,还需要去分析具体的LayoutManager的实现。
先来看RecyclerView重写的draw方法,调用了super.draw(),故这里会执行系统默认的draw流程,后面用了for循环调用mItemDecorations的onDrawOver方法。
@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);
}
...
}
再来看下onDraw方法,super.onDraw()是空方法,它主要的工作是for循环调用了mItemDecorations的onDraw方法。draw()和onDraw()中主要的工作都是和ItemDecoration相关。ItemDecoration从字面上看,可以给item添加装饰,它可以为item自定义实现很多漂亮的效果,后续也会分析它的。
@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);
}
}
本文是绘制篇的开篇,介绍了RecyclerView的绘制框架。在RecyclerView类中,只实现了RecyclerView的长宽mode是EXACTLY时的长宽测量,其余的测量布局工作都是放在LayoutManager中进行的,官方提供了三种LayoutManager,后续将一一进行分析。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。