作者:opLW
参考:RecyclerView全面的源码解析
最近用RecyclerView用的很多,打算写一系列相关的文章,做一个总结,加深理解。? 有什么不对还望指出。(RV代表RecyclerView, LM代表LayoutManager)
目录
1.onMeasure方法
2.onLayout方法
3.onDraw方法
前言: 提到绘制流程,那么离不开三个方法onMeasure,onLayout,onDraw。下面按照这个思路整理一下RecyclerView的绘制流程。不熟悉Android view绘制机制的可以自行百度或者查阅《Android开发艺术探索》第四章。
1.onMeasure方法
-
1)大体流程:
//RecyclerView @Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { // 1 没有设置LayoutManager } if (mLayout.isAutoMeasureEnabled()) { // 2 mAutoMeasure为true } else { // 3 mAutoMeasure为false } } //RecyclerView#LayoutManager public boolean isAutoMeasureEnabled() { return mAutoMeasure; }
-
2)没有设置LayoutManager
@Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); // 4 return; } .... } void defaultOnMeasure(int widthSpec, int heightSpec) { 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); }
- 4 在没有设置LayoutManager的情况下,RV会调用
defaultOnMeasure
测量并设置自身的大小,然后return
,跳出onMeasure方法。 - 感觉好像有点不对,测量完自身之后不是应该测量子view????。这点与我们所认知的不同,所以
LayoutManager
是RV的必要成分体现出来了,没有LayoutManager
的话一切免谈。
- 4 在没有设置LayoutManager的情况下,RV会调用
-
-
A 什么是AutoMeasure 根据RV源码注释简单说下,
AutoMeasure
是RV的一种机制,RV会在调用onMeasure
时,同时调用LayoutManager
的onLayoutChildren
方法,来获取子view
的大小和位置并在测量好子view之后再来设置RV的尺寸,以此来更好的支持RV的动画效果。当AutoMeasure
取值为true时,会使用这个机制。系统自带的三个LayoutManager
默认设置为true,当我们需要自定义测量工作时,需要将AutoMeasure
设置为false,并且重写LayoutManager
的onMeasure
方法。 -
B mState.mLayoutStep的三种取值以及三个dispatchLayoutStep 顾名思义,这个变量记录了当前执行到的步骤,下面看看它的取值:
取值 含义 State.STEP_START 代表尚未执行dispatchLayoutStep1() State.STEP_LAYOUT 代表执行了dispatchLayoutStep1(),尚未执行dispatchLayoutStep2() State.STEP_ANIMATIONS 代表执行了1和2,尚未执行dispatchLayoutStep3() dispatchLayoutStep1() 源代码很多,这里根据注释简要说明这一步做了什么:1.处理Adapter的更新;2.决定做哪些动画;3.保存一些相关的信息;4.必要的话,执行预布局并保存信息。执行之后更新mState.mLayoutStep为State.STEP_LAYOUT dispatchLayoutStep2() 最重要的步骤,这个方法真正的执行了对子View的测量和布局工作。执行之后更新mState.mLayoutStep为State.STEP_ANIMATIONS dispatchLayoutStep3() 这个步骤根据之前保存的动画信息,触发相应的动画效果。 -
C AutoMeasure为true时所作的工作
@Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { // 1 没有设置LayoutManager } if (mLayout.isAutoMeasureEnabled()) { final int widthMode = MeasureSpec.getMode(widthSpec); final int heightMode = MeasureSpec.getMode(heightSpec); //省略注释 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); // 5 final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; if (measureSpecModeIsExactly || mAdapter == null) { return; // 6 } if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); // 7 } mLayout.setMeasureSpecs(widthSpec, heightSpec); mState.mIsMeasuring = true; dispatchLayoutStep2(); // 8 mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); // 9 if (mLayout.shouldMeasureTwice()) { mLayout.setMeasureSpecs( MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); mState.mIsMeasuring = true; dispatchLayoutStep2(); mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); } } else { // 3 mAutoMeasure为false } }
- D 5 看到标识5处,疑惑来了???不是说设置AutoMeasure为false时,才会调用LM的onMeasure方法吗?
上面是源码中对5的注释,提到了本来应该用前面提到的RV的/** * This specific call should be considered deprecated and replaced with * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could * break existing third party code but all documentation directs developers to not * override {@link LayoutManager#onMeasure(int, int)} when * {@link LayoutManager#isAutoMeasureEnabled()} returns true.*/
defaultOnMeasure
方法代替的,但是可能会影响到一些第三方代码,所以没有。而且官文文档已经很好的引导了开发者,在AutoMeasure
为true时不要重写LM的onLayout
方法,我们看下面的源码,可以知道LM的onMeasure
方法的默认实现是调用的RV的defaultOnMeasure
。所以最终调用的还是RV的defaultOnMeasure
。这里有点不明白,如何影响到第三方代码,如有知道还望留言赐教。?
//RecyclerView#LayoutManager public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec, int heightSpec) { mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); }
- E 6 下面是标识6的代码,可以看出当RV的大小可以确定时,直接返回不再根据子View的大小来设置自身的尺寸。那什么时候RV的大小可以确定呢?就是当RV的大小设置为明确数字或者match_parent时。
final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; if (measureSpecModeIsExactly || mAdapter == null) { return; // 6 }
- F 7 在mState.mLayoutStep为State.STEP_START时执行了
dispatchLayoutStep1
。 通过前面的表格对三个步骤有了大体的了解。这里为了不脱离主线和简单,只重点介绍dispatchLayoutStep2
。
- G 8 调用
dispatchLayoutStep2
private void dispatchLayoutStep2() { startInterceptRequestLayout(); onEnterLayoutOrScroll(); mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); mAdapterHelper.consumeUpdatesInOnePass(); mState.mItemCount = mAdapter.getItemCount(); // 8.1 mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; // Step 2: Run layout mState.mInPreLayout = false; // 8.2 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(); stopInterceptRequestLayout(false); }
startInterceptRequestLayout()
和stopInterceptRequestLayout(false)
成对出现,主要作用是防止在layout的过程中,某一个子View触发了onRequestLayout
方法,从而导致多余的layout操作。- 这段代码主要获取了子View的个数(8.1)以及调用LM的
onLayoutChildren
(8.2)对子View进行测量和布局。此处也体现了RV的强大,将布局抽离出来交给LM管理,从而使得可以灵活的实现各种布局。后面会有文章介绍自定义LM。这种思想值得学习,应用到实际的代码编写中。?
- H 9 根据最后的测量结果设置RV的大小。
//RV#onMeasure mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); // 9 //RV#LM#setMeasuredDimensionFromChildren void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) { final int count = getChildCount(); if (count == 0) { mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); return; } // 9.1 mRecyclerView.mTempRect.set(minX, minY, maxX, maxY); setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec); // 9.2 }
- (9.1) 省略了代码,主要是根据前面对子View的测量和布局计算RV的大小(9.2);
- I 总结 自此mAutoMeasure为true的情况结束。主要是判断RV的尺寸是否为EXACTLY,是则直接设置,否则调用
dispatchLayoutStep1
和dispatchLayoutStep2
进行信息的初始化计算,对子View的测量和布局以及根据计算的结果设置RV的大小。下面看看mAutoMeasure为false的情况。
-
-
4)mAutoMeasure为false
@Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { // 1 没有设置LayoutManager } if (mLayout.isAutoMeasureEnabled()) { // 2 mAutoMeasure为true } else { if (mHasFixedSize) { // 10 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); return; } // 省略部分更新操作 if (mAdapter != null) { mState.mItemCount = mAdapter.getItemCount(); } else { mState.mItemCount = 0; } startInterceptRequestLayout(); mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); // 11 stopInterceptRequestLayout(false); mState.mInPreLayout = false; // clear } }
- 10 是RV留给开发者的一个优化点,可以根据需要将此值设为true,来减少不必要的测量工作从而达到优化,感兴趣的同学可自行百度。
- 11 此处调用LM
onMeasure
进行测量,可以是自定义的也可以是默认的方法。默认的方法会调用RV的defaultOnMeasure
。
-
5)总结onMeasure mAutoMeasure为true时,会涉及到子View的测量和布局(在LM的onLayoutChildren方法里)。而为false时,单纯的测量并没有布局。那么就有布局和没布局的区别了,如何解决呢?RV在onLayout方法里处理了这个问题,接着往下看。
2.onLayout方法
-
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); // 12 TraceCompat.endSection(); mFirstLayoutComplete = true; } void dispatchLayout() { if (mAdapter == null) { // 13 Log.e(TAG, "No adapter attached; skipping layout"); // leave the state in START return; } if (mLayout == null) { // 14 Log.e(TAG, "No layout manager attached; skipping layout"); // leave the state in START return; } mState.mIsMeasuring = false; if (mState.mLayoutStep == State.STEP_START) { // 15 dispatchLayoutStep1(); mLayout.setExactMeasureSpecsFrom(this); 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(); } else { // always make sure we sync them (to ensure mode is exact) mLayout.setExactMeasureSpecsFrom(this); } dispatchLayoutStep3(); // 16 }
- 12 可以看见最终调用
dispatchLayout
进行布局工作。 - 13,14 再次检查
adapter
和LM是否为空。 - 15 上面提到
onLayout
会解决布局和没布局的区别,此处会进行判断,从而调用dispatchLayoutStep1()
和dispatchLayoutStep2()
中的一个或两个进行布局工作。忘记dispatchLayoutStep2()
是干嘛的,点击传送门 - 16 最后统一调用
dispatchLayoutStep3()
进行动画相关的工作。 - 在onLayout方法里,会统一保证相关的layout方法得到调用。
- 12 可以看见最终调用
3.onDraw方法
-
public void draw(Canvas c) { super.draw(c); // 17 final int count = mItemDecorations.size(); // 18 for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); } } @Override public void onDraw(Canvas c) { super.onDraw(c); final int count = mItemDecorations.size(); // 18 for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); } }
- 17 RV重写了
View.draw
方法,在17处调用View
的draw
方法进行子View绘制的分发。 - 18 两处18看出在RV在
draw
的过程中,调用了ItemDecoration
的两个draw
相关的方法,此处也是RV的一个亮点,抽离出了ItemDecoration
,方便制作更加漂亮的界面。详细ItemDecoration
自行查看相关文章。
- 17 RV重写了
总结
RV对绘制相关的内容进行了完整编写的,同时也留下许多方法供我们自定义,很是灵活。但也很复杂,值得深入理解,后面还会有其他相关的文章介绍。RecyclerView (二) – 缓存复用机制(上)
万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。
opLW原创七言律诗,转载请注明出处