前言
RecyclerView 源码一万多行,想全部读懂学会挺麻烦的,感兴趣的可以自己去瞅瞅,这篇文章重点来看下 RecyclerView是如何一步步将每一个 ItemView 显示到屏幕上,然后再分析在显示和滑动过程中,是如何通过缓存复用来提升整体性能的。
RecyclerView本质上也是一个 自定义控件 ,因此我们可以沿着分析其 onMeasure -> onLayout -> onDraw 这 3 个方法的路线来深入研究。
绘制流程分析
onMeasure
@Override protected void onMeasure(int widthSpec, int heightSpec) { ... if (mLayout.isAutoMeasureEnabled()) { final int widthMode = MeasureSpec.getMode(widthSpec); final int heightMode = MeasureSpec.getMode(heightSpec); //mLayout(传入的 LayoutManager)的 onMeasure 方法测量RecyclerView的宽高。 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); //RecyclerView 的宽高被设置为 match_parent 或者具体值,则返回 true final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; if (measureSpecModeIsExactly || mAdapter == null) { return; } if (mState.mLayoutStep == State.STEP_START) { //动画相关 dispatchLayoutStep1(); } mLayout.setMeasureSpecs(widthSpec, heightSpec); mState.mIsMeasuring = true; //RecyclerView宽高设置为wrap_content //测量 RecyclerView 的子 View 的大小,最终确定 RecyclerView 的实际宽高。 dispatchLayoutStep2(); ... } }
onLayout
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); mFirstLayoutComplete = true; }
仅调用了 dispatchLayout() 方法,咱们朝里面瞅
dispatchLayout
void dispatchLayout() { ... mState.mIsMeasuring = false; if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); mLayout.setExactMeasureSpecsFrom(this); //测量子 View dispatchLayoutStep2(); } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) { mLayout.setExactMeasureSpecsFrom(this); //测量子 View dispatchLayoutStep2(); } else { mLayout.setExactMeasureSpecsFrom(this); } //触发动画效果 dispatchLayoutStep3(); }
如果在 onMeasure 阶段没有执行 dispatchLayoutStep2() 方法去测量子 View,则会在 onLayout 阶段重新执行。
dispatchLayoutStep2
//在此步骤中,我们对最终状态的视图进行实际布局。 //如有必要,可多次运行此步骤(例如,measure)。 private void dispatchLayoutStep2() { ... // Step 2: Run layout mState.mInPreLayout = false; mLayout.onLayoutChildren(mRecycler, mState); ... } public void onLayoutChildren(Recycler recycler, State state) { Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); }
核心逻辑是调用了 mLayout 的 onLayoutChildren 方法。
这个方法在 RecyclerView.LayoutManager 中的一个空方法,主要作用是测量 RecyclerView 内的子 View 大小,并确定它们所在的位置。
LinearLayoutManager、GridLayoutManager,以及 StaggeredLayoutManager 都分别复写了这个方法,并实现了不同方式的布局。
LinearLayoutManager.onLayoutChildren
1.@Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { ... //调用 fill 方法,完成子 View 的测量布局工作; fill(recycler, mLayoutState, state, false); ... } //重点 int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { ... while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); if (RecyclerView.VERBOSE_TRACING) { TraceCompat.beginSection("LLM LayoutChunk"); } //子 View 测量布局的真正实现,每次执行完之后需要重新计算 remainingSpace。 layoutChunk(recycler, state, layoutState, layoutChunkResult); ... if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null || !state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; // 我们保留一个单独的剩余空间,因为Mavaailable对于回收很重要 //每次循环之后,都将remainingSpace减去已消费的size remainingSpace -= layoutChunkResult.mConsumed; } ... return start - layoutState.mAvailable; }
在 onLayoutChildren 中调用 fill 方法,完成子 View 的测量布局工作;
在 fill 方法中通过 while 循环判断是否还有剩余足够空间来绘制一个完整的子 View;
layoutChunk 方法中是子 View 测量布局的真正实现,每次执行完之后需要重新计算 remainingSpace。
layoutChunk
layoutChunk 是一个非常核心的方法,这个方法执行一次就填充一个 ItemView 到 RecyclerView,部分代码如下:
1.void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { //从缓存(Recycler)中取出子 ItemView。 View view = layoutState.next(recycler); ... RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); //然后调用 addView 或者 addDisappearingView 将子 ItemView 添加到 RecyclerView 中。 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); } } //测量被添加的 RecyclerView 中的子 ItemView 的宽高。 measureChildWithMargins(view, 0, 0); ... //根据所设置的 Decoration、Margins 等所有选项确定子 ItemView 的显示位置。 layoutDecoratedWithMargins(view, left, top, right, bottom); ... }
onDraw
测量和布局都完成之后,就剩下最后的绘制操作了。
@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); } }
如果有添加 ItemDecoration,则循环调用所有的 Decoration 的 onDraw 方法,将其显示。至于所有的子 ItemView 则是通过 Android 渲染机制递归的调用子 ItemView 的 draw 方法显示到屏幕上。
绘制流程小结
RecyclerView 会将测量 onMeasure 和布局 onLayout 的工作委托给 LayoutManager 来执行,不同的 LayoutManager 会有不同风格的布局显示,这是一种策略模式。如下图: