一、RecyclerView 的复用机制:核心中的核心

RecyclerView 的复用机制,说白了就是「不浪费」。

我刚开始做 Android 开发时,用的还是 ListView。那时候有个痛点——滑快了容易卡顿,尤其是 item 布局复杂的时候。后来 Google 推出了 RecyclerView,我第一时间就切过去了。为什么?因为它把「复用」这件事做到了极致。

1.1 ViewHolder 与 Recycler 的配合

RecyclerView 的复用,核心靠两个东西:ViewHolderRecycler

  • ViewHolder:缓存 item 的布局引用,避免重复 findViewById。
  • Recycler:管理 ViewHolder 的回收与复用。

你想想看,当列表滑动时,屏幕外的 item 会被回收,屏幕内需要展示的 item 会从缓存池里取。如果缓存池里有现成的,直接拿来用;没有,才去创建新的。

关键点:RecyclerView 有四级缓存:

  1. Scrap:正在被操作的 ViewHolder,比如动画期间。
  2. Cache:最近离开屏幕的 ViewHolder,默认容量 2 个。
  3. ViewCacheExtension:开发者自定义缓存,我很少用。
  4. RecycledViewPool:跨 Adapter 共享的缓存池,按 viewType 分组。

嗯,这里要注意:Cache 和 RecycledViewPool 的区别。Cache 里的 ViewHolder 是「干净的」,不需要重新绑定数据;而 RecycledViewPool 里的 ViewHolder 需要重新走 onBindViewHolder。

1.2 复用流程的 SVG 图

RecyclerView 复用流程 滑动方向 → 屏幕可见区域(LayoutManager 管理) 滑出屏幕 回收 四级缓存结构 Scrap(操作中) Cache(2个) 自定义扩展 Pool(按type) Cache 中的 ViewHolder 无需重新绑定,Pool 中的需要走 onBindViewHolder

二、onBindViewHolder 与绘制的关系

很多人以为 onBindViewHolder 只是「设置数据」,其实它跟绘制流程有直接关系。

我遇到过一个问题:列表滑动时,item 的图片加载导致掉帧。排查后发现,onBindViewHolder 里做了太多耗时操作,比如图片解码、布局计算。这些操作会阻塞主线程,直接影响绘制。

2.1 onBindViewHolder 的调用时机

onBindViewHolder 是在 UI 线程调用的。它发生在 RecyclerView 的 layout 阶段,具体来说是在 LayoutManager 的 onLayoutChildren 方法里。

绘制流程是:measure → layout → draw。onBindViewHolder 属于 layout 阶段的一部分。如果 onBindViewHolder 执行时间过长,layout 阶段就会变慢,draw 阶段只能等。

我的建议:onBindViewHolder 里只做「数据赋值」和「轻量 UI 更新」。图片加载、网络请求、复杂计算,统统放到异步线程或者使用 Preload 机制。

2.2 数据绑定与绘制的时序

阶段 操作 是否影响绘制
onCreateViewHolder 创建 ViewHolder,inflate 布局 间接影响(布局创建)
onBindViewHolder 绑定数据,更新 UI 直接影响(layout 阶段)
onViewAttachedToWindow item 附加到窗口 触发动画或额外操作
onViewDetachedFromWindow item 从窗口移除 清理资源

你看,onBindViewHolder 是唯一一个「每次 item 进入屏幕都会执行」的方法。所以它的性能,直接决定了列表的流畅度。

2.3 避免重复绑定的技巧

我曾经接手过一个项目,列表滑动时卡得不行。一看代码,onBindViewHolder 里每次都调用 setImageResource,而且图片资源是运行时从网络加载的。每次绑定都重新请求,这能不卡吗?

优化方案很简单:

  • 使用 DiffUtil 判断数据是否变化,避免不必要的绑定。
  • 图片加载用 Glide 或 Coil,它们内部有缓存机制。
  • 如果 item 布局复杂,考虑 ViewStub 延迟加载不常用的部分。

注意:不要以为 setVisibility 是轻量操作。频繁调用 setVisibility 会触发 requestLayout,导致重新测量和布局。在 onBindViewHolder 里,尽量用 setAlpha 或 setTranslationX 代替。

三、ItemAnimator 与绘制动画

ItemAnimator 是 RecyclerView 的「动画引擎」。它负责 item 添加、删除、移动、变化时的过渡动画。

我刚开始用 RecyclerView 时,觉得默认的动画就挺好。后来做聊天列表,需要自定义消息出现动画,才发现 ItemAnimator 的威力。

3.1 ItemAnimator 的工作原理

ItemAnimator 本质上是一个 动画调度器。它会在 item 状态变化时,记录旧状态和新状态,然后计算差值,驱动动画。

绘制流程中,ItemAnimator 的动画是在 draw 阶段 之后触发的。具体来说:

  1. RecyclerView 完成 layout 和 draw。
  2. ItemAnimator 开始执行动画(比如位移、缩放、淡入淡出)。
  3. 动画过程中,RecyclerView 会不断请求重绘(invalidate)。

嗯,这里有个坑:如果动画执行时间过长,或者动画数量过多,会导致掉帧。因为每次动画帧都会触发重绘。

3.2 自定义 ItemAnimator 的实践

我做过一个需求:列表中的 item 从底部滑入,同时透明度从 0 变到 1。默认的 DefaultItemAnimator 不支持这种组合动画,所以我自定义了一个:

class SlideInItemAnimator : DefaultItemAnimator() {
    override fun animateAdd(holder: ViewHolder): Boolean {
        holder.itemView.apply {
            alpha = 0f
            translationY = 100f
        }
        return super.animateAdd(holder)
    }

    override fun animateAddImpl(holder: ViewHolder) {
        holder.itemView.animate()
            .alpha(1f)
            .translationY(0f)
            .setDuration(300)
            .setInterpolator(DecelerateInterpolator())
            .withEndAction {
                dispatchAddFinished(holder)
            }
            .start()
    }
}

这段代码看起来简单,但要注意:动画结束后必须调用 dispatchAddFinished,否则 RecyclerView 会认为动画还没完成,导致 item 状态异常。

3.3 ItemAnimator 与绘制性能

ItemAnimator 的动画本质上是属性动画(ValueAnimator)。属性动画每帧都会回调,触发 View 的 invalidate,进而走一遍 draw 流程。

如果动画涉及大量 item,比如一次性添加 20 条数据,每个 item 都执行动画,那 draw 阶段的压力会非常大。

我的经验:批量操作时,可以暂时禁用动画:

recyclerView.itemAnimator = null
// 执行批量操作
adapter.submitList(newList)
// 操作完成后恢复动画
recyclerView.itemAnimator = DefaultItemAnimator()

这样能避免动画导致的卡顿。

3.4 动画与复用的冲突

你想想看,如果 item 正在执行动画,突然被回收了,会发生什么?

答案是:动画会被强制结束,item 直接消失。这会导致视觉上的「闪烁」。

我曾经踩过这个坑:列表快速滑动时,item 的淡出动画还没执行完,就被回收了。结果用户看到的是 item 突然消失,而不是平滑淡出。

解决方案:在 onViewDetachedFromWindow 中取消动画,或者使用 isRecyclable 标记暂时不可回收的 item。

override fun onViewDetachedFromWindow(holder: ViewHolder) {
    super.onViewDetachedFromWindow(holder)
    holder.itemView.animate().cancel()
}

四、总结

RecyclerView 的复用机制、onBindViewHolder 和 ItemAnimator,这三者共同决定了列表的绘制性能。

  • 复用机制:减少 View 创建,提升滑动流畅度。
  • onBindViewHolder:数据绑定的效率直接影响 layout 阶段。
  • ItemAnimator:动画虽好,但要注意性能开销。

我个人习惯是:在项目初期就做好性能规划。比如使用 DiffUtil 减少绑定次数,用异步加载图片,动画尽量轻量。这样后期就不会被性能问题追着跑。

好了,这一章的内容就到这里。记住:RecyclerView 的绘制不是孤立的,它跟复用、绑定、动画紧密相关。理解了这三者的关系,你就能写出流畅的列表了。

公众号:蓝海资料掘金营,微信deep3321