Android RecyclerView 绘制流程及Recycler缓存(上)

简介: RecyclerView 源码一万多行,想全部读懂学会挺麻烦的,感兴趣的可以自己去瞅瞅,这篇文章重点来看下 RecyclerView是如何一步步将每一个 ItemView 显示到屏幕上,然后再分析在显示和滑动过程中,是如何通过缓存复用来提升整体性能的。 RecyclerView本质上也是一个 自定义控件 ,因此我们可以沿着分析其 onMeasure -> onLayout -> onDraw 这 3 个方法的路线来深入研究。

前言




     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 会有不同风格的布局显示,这是一种策略模式。如下图:


微信图片_20220522155635.png




相关文章
|
3月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
41 2
|
12天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
38 15
Android 系统缓存扫描与清理方法分析
|
15天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
66 6
|
13天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
16天前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
Android面试高频知识点(4) 详解Activity的启动流程
22 3
|
17天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
19 2
|
29天前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
33 3
|
16天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
13 0
|
2月前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
讲解Activity的启动流程了,Activity的启动流程相对复杂一下,涉及到了Activity中的生命周期方法,涉及到了Android体系的CS模式,涉及到了Android中进程通讯Binder机制等等, 首先介绍一下Activity,这里引用一下Android guide中对Activity的介绍:
49 4
|
2月前
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
132 9