8、RecyclerView进阶:LayoutManager、Adapter、ViewHolder、ItemDecoration、点击事件处理
RecyclerView 这东西,说实话,是 Android 开发里绕不开的一个坎。基础用法大家都会,但真要把它玩明白,还是得花点功夫。今天我就把 RecyclerView 进阶的几个核心点掰开揉碎了讲一讲。
我个人习惯把 RecyclerView 比作一个「乐高积木系统」。它本身只负责「回收」和「复用」,真正决定长什么样的,是 LayoutManager、Adapter、ViewHolder 这些零件。你想想看,是不是这个理?
核心要点:RecyclerView 的灵活性来自于它的组件化设计。每个组件各司其职,组合起来就能应对各种复杂场景。
8.1 LayoutManager:布局的指挥官
LayoutManager 决定了 item 在屏幕上怎么排列。系统提供了三个现成的:LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager。但说实话,实际项目中经常需要自定义。
我在项目中遇到过这样一个需求:做一个类似「时间轴」的列表,左边是时间线,右边是内容。用 LinearLayoutManager 显然不行,因为每个 item 的左边都要对齐时间线。最后我写了个自定义 LayoutManager,重写了 onLayoutChildren 方法,手动计算每个 item 的位置。
class TimelineLayoutManager : RecyclerView.LayoutManager() {
override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
return RecyclerView.LayoutParams(
RecyclerView.LayoutParams.MATCH_PARENT,
RecyclerView.LayoutParams.WRAP_CONTENT
)
}
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
detachAndScrapAttachedViews(recycler)
val parentWidth = width
val parentHeight = height
var top = paddingTop
for (i in 0 until itemCount) {
val view = recycler.getViewForPosition(i)
addView(view)
measureChildWithMargins(view, 0, 0)
val left = paddingLeft
val right = left + getDecoratedMeasuredWidth(view)
val bottom = top + getDecoratedMeasuredHeight(view)
layoutDecoratedWithMargins(view, left, top, right, bottom)
top = bottom
}
}
}
嗯,这里要注意:自定义 LayoutManager 时,一定要处理好 detachAndScrapAttachedViews 和 recycler.getViewForPosition 的配合。否则很容易出现 item 显示错乱的问题。
小技巧:如果你只是想让列表「横着走」,直接用 LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) 就行,别自己造轮子。
8.2 Adapter 与 ViewHolder:数据与视图的桥梁
Adapter 负责把数据变成 View,ViewHolder 负责缓存这些 View。说白了,Adapter 是「生产车间」,ViewHolder 是「仓库」。
我建议你把 ViewHolder 写得「胖」一点。什么意思?就是把 findViewById、setText、setOnClickListener 这些操作都封装在 ViewHolder 里。Adapter 只负责传数据,不负责 UI 操作。
class MyAdapter(private val dataList: List<String>) :
RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_layout, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(dataList[position])
}
override fun getItemCount(): Int = dataList.size
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textView: TextView = itemView.findViewById(R.id.text_view)
fun bind(text: String) {
textView.text = text
}
}
}
你看,Adapter 里干干净净,所有 UI 逻辑都在 ViewHolder 里。这样后期改样式,只需要改 ViewHolder,不用动 Adapter。
注意:不要在 onBindViewHolder 里做耗时操作。这个方法会被频繁调用,任何卡顿都会直接体现在列表滑动上。我曾经就因为在这里加载图片,导致列表卡成 PPT。
8.3 ItemDecoration:给列表加点「装饰」
ItemDecoration 是个好东西,但很多人只用它来画分割线。其实它能做的事情远不止这些。
我举个例子:你想在列表的每个 item 之间加一个「间距」,很多人会直接在 item 布局里写 margin。但这样有个问题——第一个 item 的顶部也会多出 margin。用 ItemDecoration 就能完美解决。
class SpaceItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View,
parent: RecyclerView, state: RecyclerView.State) {
// 第一个 item 不添加顶部间距
if (parent.getChildAdapterPosition(view) != 0) {
outRect.top = space
}
}
}
除了间距,你还可以用它画「分组标题」、「水印」、「选中高亮」等等。说白了,只要跟 item 位置相关的绘制,都可以交给 ItemDecoration。
提示:ItemDecoration 的 onDraw 方法是在 item 内容之下绘制的,onDrawOver 是在 item 内容之上绘制的。想清楚你要的效果,再决定用哪个。
8.4 点击事件处理:让列表「活」起来
RecyclerView 没有提供现成的点击事件接口,需要自己实现。常见的做法有两种:
- 在 ViewHolder 里设置点击监听——简单直接,适合 item 内部有多个可点击控件的情况。
- 在 Adapter 里设置回调接口——解耦性好,适合把点击事件传递给 Activity 或 Fragment。
我个人更推荐第二种。为什么呢?因为第一种方式会让 ViewHolder 持有 Activity 的引用,容易造成内存泄漏。
class MyAdapter(
private val dataList: List<String>,
private val onItemClick: (position: Int) -> Unit
) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(dataList[position])
holder.itemView.setOnClickListener {
onItemClick(position)
}
}
}
这样写,点击事件的处理逻辑完全由调用方决定,Adapter 只负责「通知」。你想想看,是不是更灵活?
避坑指南:我曾经在 onBindViewHolder 里直接 new 了一个 OnClickListener,结果列表滑动时频繁创建对象,导致 GC 频繁触发。后来改成在 ViewHolder 里统一设置监听,问题就解决了。
8.5 知识体系总览
下面这张图是我自己整理的 RecyclerView 进阶知识体系,你可以对照着看看自己掌握了多少。
这张图把 RecyclerView 进阶的五个核心模块都串起来了。你仔细看看,每个模块之间其实是有联系的。比如 ItemDecoration 的绘制依赖于 LayoutManager 的布局结果,点击事件的处理又依赖于 ViewHolder 的绑定时机。
好了,关于 RecyclerView 进阶的内容就讲到这里。这些知识点我在实际项目中反复用过,每一个都是踩过坑才总结出来的。希望你能少走一些弯路。