Android RecyclerView 绘制流程及Recycler缓存

}

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

@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,部分代码如下:

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©;

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

缓存复用原理 Recycler

===============

缓存复用是 RecyclerView 中另一个非常重要的机制,这套机制主要实现了 ViewHolder 的缓存以及复用。

核心代码是在 Recycler 中完成的,它是 RecyclerView 中的一个内部类,主要用来缓存屏幕内 ViewHolder 以及部分屏幕外 ViewHolder,部分代码如下:

public final class Recycler {

//未与RecyclerView分离的ViewHolder列表

final ArrayList mAttachedScrap = new ArrayList<>();

//与RecyclerView分离的ViewHolder列表

ArrayList mChangedScrap = null;

//ViewHolder缓存列表

final ArrayList mCachedViews = new ArrayList();

//ViewHolder缓存池

RecycledViewPool mRecyclerPool;

//开发者可以控制的ViewHolder缓存的帮助类

private ViewCacheExtension mViewCacheExtension;

//默认情况下缓存个数是 2

static final int DEFAULT_CACHE_SIZE = 2;

}

Recycler 的缓存机制就是通过上图中的这些数据容器来实现的,实际上 Recycler 的缓存也是分级处理的,根据访问优先级从上到下可以分为 4 级,如下:

  • 第一级缓存 mAttachedScrap&mChangedScrap

  • 第二级缓存 mCachedViews

  • 第三级缓存 ViewCacheExtension

  • 第四级缓存 RecycledViewPool

各级缓存功能


RecyclerView 之所以要将缓存分成这么多块,是为了在功能上进行一些区分,并分别对应不同的使用场景。

第一级缓存 mAttachedScrap&mChangedScrap

是两个名为 Scrap 的 ArrayList,这两者主要用来缓存屏幕内的 ViewHolder。为什么屏幕内的 ViewHolder 需要缓存呢?

做过 App 开发的应该都熟悉通过下拉刷新列表中的内容,当刷新被触发时,只需要在原有的 ViewHolder 基础上进行重新绑定新的数据 data 即可,而这些旧的 ViewHolder 就是被保存在 mAttachedScrap 和 mChangedScrap 中。实际上当我们调用 RV 的 notifyXXX 方法时,就会向这两个列表进行填充,将旧 ViewHolder 缓存起来。

第二级缓存 mCachedViews

它用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存个数是 2,不过可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO(先进先出) 的规则将旧 ViewHolder 抛弃,然后添加新的 ViewHolder。

通常情况下刚被移出屏幕的 ViewHolder 有可能接下来马上就会使用到,所以 RecyclerView 不会立即将其设置为无效 ViewHolder,而是会将它们保存到 cache 中,但又不能将所有移除屏幕的 ViewHolder 都视为有效 ViewHolder,所以它的默认容量只有 2 个。

第三级缓存 ViewCacheExtension

这是 RecyclerView 预留给开发人员的一个抽象类,在这个类中只有一个抽象方法。

public abstract static class ViewCacheExtension {

@Nullable

public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,

int type);

}

开发人员可以通过继承 ViewCacheExtension,并复写抽象方法 getViewForPositionAndType 来实现自己的缓存机制。一般情况下我们不会自己实现也不建议自己去添加缓存逻辑,因为这个类的使用门槛较高(牛掰的人请忽略)。

第四级缓存 RecycledViewPool

RecycledViewPool 同样是用来缓存屏幕外的 ViewHolder,当 mCachedViews 中的个数已满(默认为 2),则从 mCachedViews 中淘汰出来的 ViewHolder 会先缓存到 RecycledViewPool 中。ViewHolder 在被缓存到 RecycledViewPool 时,会将内部的数据清理,因此从 RecycledViewPool 中取出来的 ViewHolder 需要重新调用 onBindViewHolder 绑定数据。这就同最早的 ListView 中的使用 ViewHolder 复用 convertView 的道理是一致的,因此 RV 也算是将 ListView 的优点完美的继承过来。

RecycledViewPool 还有一个重要功能,官方对其有如下解释:

RecycledViewPool 允许您在多个 RecyclerView 之间共享视图。

如果要跨 RecyclerViews 回收视图,请创建 RecycledViewPool 的实例并使用RecyclerView.setRecycledViewPool(RecycledViewPool)。

最后

小编深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

链接:https://pan.baidu.com/s/1XmbD0ygpg887zNAAPz1giQ?pwd=1234
使用 ViewHolder 复用 convertView 的道理是一致的,因此 RV 也算是将 ListView 的优点完美的继承过来。

RecycledViewPool 还有一个重要功能,官方对其有如下解释:

RecycledViewPool 允许您在多个 RecyclerView 之间共享视图。

如果要跨 RecyclerViews 回收视图,请创建 RecycledViewPool 的实例并使用RecyclerView.setRecycledViewPool(RecycledViewPool)。

最后

小编深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-8iu6fTDh-1717474964152)]

链接:https://pan.baidu.com/s/1XmbD0ygpg887zNAAPz1giQ?pwd=1234
提取码:1234

相关推荐

  1. Android View绘制流程详解

    2024-06-11 15:54:03       37 阅读
  2. Android中View的绘制流程

    2024-06-11 15:54:03       22 阅读

最近更新

  1. linux:命令执行过程【图表】

    2024-06-11 15:54:03       0 阅读
  2. 系统架构设计师——网络设计

    2024-06-11 15:54:03       0 阅读
  3. SSL证书到期自动巡检脚本-推送钉钉告警

    2024-06-11 15:54:03       1 阅读
  4. 如何才能在Linux下编写驱动程序

    2024-06-11 15:54:03       1 阅读
  5. Tomcat打破双亲委派模型的方式

    2024-06-11 15:54:03       1 阅读
  6. C++惯用法: 通过std::decltype来SFINAE掉表达式

    2024-06-11 15:54:03       1 阅读
  7. HTTP 范围Range请求

    2024-06-11 15:54:03       1 阅读

热门阅读

  1. 1049. 最后一块石头的重量 II

    2024-06-11 15:54:03       9 阅读
  2. Web前端浪漫源码:编织梦想与爱的交织乐章

    2024-06-11 15:54:03       9 阅读
  3. 重新学习STM32(1)GPIO

    2024-06-11 15:54:03       6 阅读
  4. 将字符串转换为Python数据类型

    2024-06-11 15:54:03       13 阅读
  5. 代码随想录——数组

    2024-06-11 15:54:03       9 阅读
  6. CVE-2024-1086漏洞处理

    2024-06-11 15:54:03       11 阅读
  7. ArrayList源码解析

    2024-06-11 15:54:03       8 阅读
  8. USB (5)

    USB (5)

    2024-06-11 15:54:03      9 阅读
  9. Python 日期和时间

    2024-06-11 15:54:03       9 阅读
  10. API接口:解锁社交电商的创新潜力

    2024-06-11 15:54:03       14 阅读