一、RecyclerView 的复用机制:核心中的核心
RecyclerView 的复用机制,说白了就是「不浪费」。
我刚开始做 Android 开发时,用的还是 ListView。那时候有个痛点——滑快了容易卡顿,尤其是 item 布局复杂的时候。后来 Google 推出了 RecyclerView,我第一时间就切过去了。为什么?因为它把「复用」这件事做到了极致。
1.1 ViewHolder 与 Recycler 的配合
RecyclerView 的复用,核心靠两个东西:ViewHolder 和 Recycler。
- ViewHolder:缓存 item 的布局引用,避免重复 findViewById。
- Recycler:管理 ViewHolder 的回收与复用。
你想想看,当列表滑动时,屏幕外的 item 会被回收,屏幕内需要展示的 item 会从缓存池里取。如果缓存池里有现成的,直接拿来用;没有,才去创建新的。
关键点:RecyclerView 有四级缓存:
- Scrap:正在被操作的 ViewHolder,比如动画期间。
- Cache:最近离开屏幕的 ViewHolder,默认容量 2 个。
- ViewCacheExtension:开发者自定义缓存,我很少用。
- RecycledViewPool:跨 Adapter 共享的缓存池,按 viewType 分组。
嗯,这里要注意:Cache 和 RecycledViewPool 的区别。Cache 里的 ViewHolder 是「干净的」,不需要重新绑定数据;而 RecycledViewPool 里的 ViewHolder 需要重新走 onBindViewHolder。
1.2 复用流程的 SVG 图
二、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 阶段 之后触发的。具体来说:
- RecyclerView 完成 layout 和 draw。
- ItemAnimator 开始执行动画(比如位移、缩放、淡入淡出)。
- 动画过程中,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 的绘制不是孤立的,它跟复用、绑定、动画紧密相关。理解了这三者的关系,你就能写出流畅的列表了。