RecyclerView 原理浅析

5 阅读7分钟

当我们深入探讨RecyclerView的原理时,实际上是在解析一个高度模块化、性能优异的UI组件的内部工作机制。它的设计哲学是  “分离关注点” ,将布局、动画、数据、回收等核心逻辑解耦,使得它既灵活又高效。

一、整体架构:四大核心组件

RecyclerView本身并不直接管理Item的布局、动画或数据绑定,而是将这些职责委托给以下几个关键对象:

  • Adapter:负责将数据与具体的Item视图绑定,同时负责创建ViewHolder。它扮演了数据源与UI之间的桥梁。
  • LayoutManager:负责测量和布局Item,决定Item在屏幕上的排列方式(线性、网格、瀑布流等)。它也负责处理滚动逻辑。
  • ItemAnimator:负责在Item添加、移除或变化时,为这些操作添加动画效果。
  • Recycler:这是回收复用机制的核心,管理着所有ViewHolder的缓存与复用。

它们的协作关系可以这样理解:当RecyclerView需要展示一个Item时,它会向Recycler请求一个ViewHolder;如果缓存中没有,Recycler会通过AdapteronCreateViewHolder创建新的,然后Adapter通过onBindViewHolder填充数据;接着LayoutManager将这个Item布局到屏幕上;如果Item发生变化,ItemAnimator负责运行动画。

二、回收复用机制:四级缓存

RecyclerView之所以能流畅滚动,核心在于其多级缓存机制。这由Recycler类实现,它维护了几个不同层级的缓存池,查找顺序如下:

1. mAttachedScrap(一级缓存)

  • 存储位置:这是一个ArrayList,保存着屏幕内可见范围但被临时分离(detach)的ViewHolder
  • 触发时机:当RecyclerView需要重新布局(如调用notifyDataSetChanged())或执行动画时,它会将屏幕上的Item暂时detach,放入mAttachedScrap。这些ViewHolder并没有真正与RecyclerView分离,只是标记为“待用”。
  • 复用特性:因为ViewHolder仍然持有原来的数据,所以复用时不需重新绑定(不调用onBindViewHolder)。布局完成后,它们会被重新attach回原来的位置。

2. mCachedViews(二级缓存)

  • 存储位置:默认大小为2的ArrayList,保存着刚刚滑出屏幕的ViewHolder
  • 触发时机:当一个Item完全滑出屏幕时,它会被移入mCachedViews
  • 复用特性:这些ViewHolder仍然保持着完整的数据状态,因此被复用时也不需重新绑定。这使得快速往回滑动时,Item可以瞬间恢复显示。

3. mViewCacheExtension(三级缓存)

  • 存储位置:这是一个预留的扩展点,允许开发者自定义缓存策略。开发者可以实现自己的ViewCacheExtension来控制ViewHolder的存取。
  • 实际使用:在实际开发中,绝大多数情况下我们不会用到它。

4. mRecycledViewPool(四级缓存)

  • 存储位置:一个按viewType分组的SparseArray<ArrayList<ViewHolder>>。每个viewType的默认缓存上限是5个。
  • 触发时机:当mCachedViews已满,且又有新的ViewHolder需要缓存时,最早进入mCachedViews的ViewHolder会被移入RecycledViewPool。池中的ViewHolder数据会被清空(即调用onViewRecycled)。
  • 复用特性:从池中取出的ViewHolder,因为数据已被清空,所以必须重新绑定数据(调用onBindViewHolder)。
  • 重要特性:多个RecyclerView可以共享同一个RecycledViewPool,这在嵌套或多列表场景中能显著减少ViewHolder的创建开销。

5. 查找流程:

LayoutManager需要一个指定位置的ViewHolder时,它会调用RecyclergetViewForPosition。该方法依次检查:

  1. mAttachedScrap(按position/id匹配)
  2. mCachedViews(按position匹配)
  3. mViewCacheExtension
  4. mRecycledViewPool(按viewType匹配)
    如果都未找到,则通过Adapter.createViewHolder创建一个新的,然后调用Adapter.bindViewHolder绑定数据。

三、布局与测量过程

1. 大致流程

RecyclerView的布局与测量主要由LayoutManager驱动。:

  • onMeasureRecyclerView首先会测量自己。它根据LayoutManager的测量属性(如是否支持自动测量)来决定自己的尺寸。如果LayoutManager支持自动测量(isAutoMeasureEnabled()返回true),RecyclerView会调用LayoutManageronMeasure来测量所有子View,并根据子View的尺寸确定自己的尺寸。
  • onLayoutRecyclerView调用LayoutManageronLayoutChildren方法。这是布局的核心,LayoutManager会在此方法中决定如何放置子View。它通常会调用fill方法,根据当前的滚动偏移量,从某个起始位置开始,不断向Recycler请求ViewHolder,并将它们添加到布局中,直到填满屏幕。
  • 回收:在布局过程中,LayoutManager还会调用RecyclerrecycleViews来回收那些滑出屏幕的ViewHolder。回收的ViewHolder会根据优先级进入mCachedViewsRecycledViewPool

2. 预取(Prefetch)机制

在Android 5.0之后,RecyclerView引入了预取机制以优化滑动流畅性。当RecyclerView处于空闲状态时,它会预测下一个将要显示的Item,并提前创建和绑定该Item的ViewHolder。这样,当用户滑动到时,ViewHolder已经就绪,从而减少卡顿。这一机制由GapWorker类实现。

四、数据更新与动画实现

RecyclerView支持精准的局部更新,这是通过AdapternotifyItem*系列方法触发的。其内部原理涉及  “预布局”  与  “后布局”  两个阶段:

  1. 预布局:当调用notifyItemRemoved(2)时,RecyclerView会先执行一次预布局。在这次布局中,LayoutManager会为所有Item分配位置,但被删除的Item(位置2)仍然可见,只是标记为“即将移除”。同时,它会测量被删除Item之后的所有Item的新位置。这为动画提供了起始状态。
  2. 记录变化RecyclerView记录每个Item在预布局和后布局中的位置变化。
  3. 后布局:接着执行一次真正的布局,此时被删除的Item不再存在,其他Item移动到新位置。
  4. 运行动画ItemAnimator根据记录的位置变化,为每个Item生成动画。例如,被删除的Item会执行淡出动画,而后续Item则会执行向上移动的动画。

对于notifyItemChanged,如果使用了payloadRecyclerView会调用AdapteronBindViewHolder(holder, position, payloads),让开发者只更新部分UI,从而实现控件级别的刷新。

五、触摸事件与滑动机制

RecyclerView的滑动是由LayoutManagerRecyclerView共同处理的:

  1. 事件拦截RecyclerViewonInterceptTouchEvent会判断是否应该拦截触摸事件。如果识别为滚动操作(如手指移动距离超过滑动阈值),它会开始拦截事件。
  2. 事件处理:在onTouchEvent中,RecyclerView计算手指滑动的距离,并调用scrollByInternal。最终,scrollByInternal会调用LayoutManagerscrollHorizontallyByscrollVerticallyBy
  3. LayoutManager滑动LayoutManagerscrollBy方法中,会计算出需要移动的像素距离,然后调用fill方法来填充新出现的Item,同时回收滑出屏幕的Item。
  4. 惯性滑动:当手指抬起时,RecyclerView通过ViewFlinger(一个Runnable)来处理惯性滑动。它会根据手指速度计算剩余滑动距离,并逐步滑动到目标位置。在每一帧中,ViewFlinger都会触发一次新的布局,直到滑动结束。

六、嵌套滚动(Nested Scrolling)

为了支持嵌套滚动(如CoordinatorLayout中的联动效果),RecyclerView实现了NestedScrollingChild接口。在滑动过程中,它会先通知父View(如NestedScrollingParent)准备滚动,然后父View可以消费部分或全部滚动距离,剩下的由RecyclerView自己处理。同样,在惯性滑动时,也会通过嵌套滚动机制将速度分发给父View。

七、优化原理

  • 复用:通过四级缓存最大化地减少onCreateViewHolderonBindViewHolder的调用。
  • 预取:在空闲时提前加载下一个Item,减少滑动时的等待时间。
  • 稳定ID:通过setHasStableIdsRecyclerView能准确追踪Item身份,优化动画和刷新。
  • DiffUtil:利用算法计算最小更新范围,避免不必要的重绘。
  • 布局优化:通过setHasFixedSize避免不必要的重新测量,通过ItemDecorationLayoutManager的优化减少测量次数。

八、总结

RecyclerView的原理可以概括为:一个高度可定制的视图容器,通过LayoutManagerItemAnimatorAdapter的分离设计,结合多级缓存与预取机制,实现了高效的滚动性能和灵活的UI表现。