RecyclerView
本教程共 100 篇 · 第 60 篇 · 更新于 2026-07-28 · 约 9 分钟阅读
60. RecyclerView
本节目标:掌握 RecyclerView 的三大组成部分(Adapter、ViewHolder、LayoutManager),能从零实现一个可滚动的列表,理解视图复用原理。
为什么用 RecyclerView
传统 ListView 性能差、不支持网格、动画难做。RecyclerView 是它的升级版,通过复用 ViewHolder大幅提升性能,还支持横向滑动、网格、瀑布流等多种布局。
打个比方:ListView 像一次性筷子,每行都造一个新的;RecyclerView 像可重复使用的碗,滚出去的碗洗干净给新行用。
三大组成部分
RecyclerView 需要三个角色配合:
- Adapter(适配器):把数据绑定到视图上。
- ViewHolder(视图持有者):缓存每行的控件引用,避免重复
findViewById。 - LayoutManager(布局管理器):决定每行怎么排列(线性、网格、瀑布流)。
添加依赖
dependencies {
implementation("androidx.recyclerview:recyclerview:1.3.2")
}
完整实现
第一步:数据类
data class Contact(val id: Int, val name: String, val phone: String)
第二步:列表项布局
res/layout/item_contact.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#666666" />
</LinearLayout>
第三步:ViewHolder
class ContactViewHolder(
private val binding: ItemContactBinding,
private val onItemClick: (Contact) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
fun bind(contact: Contact) {
binding.tvName.text = contact.name
binding.tvPhone.text = contact.phone
binding.root.setOnClickListener { onItemClick(contact) }
}
}
第四步:Adapter
class ContactAdapter(
private val contacts: List<Contact>,
private val onItemClick: (Contact) -> Unit
) : RecyclerView.Adapter<ContactViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder {
val binding = ItemContactBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return ContactViewHolder(binding, onItemClick)
}
override fun onBindViewHolder(holder: ContactViewHolder, position: Int) {
holder.bind(contacts[position])
}
override fun getItemCount(): Int = contacts.size
}
第五步:使用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val contacts = listOf(
Contact(1, "张三", "13800001111"),
Contact(2, "李四", "13800002222"),
Contact(3, "王五", "13800003333")
)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = ContactAdapter(contacts) { contact ->
Toast.makeText(this@MainActivity, contact.name, Toast.LENGTH_SHORT).show()
}
}
}
}
XML 里加 RecyclerView:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
LayoutManager 布局管理器
| LayoutManager | 效果 |
|---|---|
LinearLayoutManager | 线性列表(竖直/水平) |
GridLayoutManager | 网格(指定列数) |
StaggeredGridLayoutManager | 瀑布流(不等高) |
// 网格,3 列
recyclerView.layoutManager = GridLayoutManager(this, 3)
// 水平列表
recyclerView.layoutManager = LinearLayoutManager(
this, LinearLayoutManager.HORIZONTAL, false
)
// 瀑布流,2 列
recyclerView.layoutManager = StaggeredGridLayoutManager(
2, StaggeredGridLayoutManager.VERTICAL
)
ItemDecoration 分隔线
class DividerDecoration(context: Context) : RecyclerView.ItemDecoration() {
private val divider = ContextCompat.getDrawable(context, R.drawable.divider)
override fun getItemOffsets(
outRect: Rect, view: View,
parent: RecyclerView, state: RecyclerView.State
) {
outRect.bottom = 1 // 每项底部留 1px 间距
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + divider?.intrinsicHeight ?: 0
divider?.setBounds(left, top, right, bottom)
divider?.draw(canvas)
}
}
}
recyclerView.addItemDecoration(DividerDecoration(this))
Tip不想手写可以用
DividerItemDecoration,它自带默认分隔线:val decoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL) recyclerView.addItemDecoration(decoration)
数据更新
直接改数据后调 notifyDataSetChanged() 会让整个列表重建,性能差且没有动画。用 ListAdapter + DiffUtil 更高效(下一章详讲)。
简单方式:
// 通知指定位置插入
adapter.notifyItemInserted(contacts.size - 1)
// 通知指定位置删除
adapter.notifyItemRemoved(position)
// 通知指定位置变化
adapter.notifyItemChanged(position)
// 通知范围变化
adapter.notifyItemRangeChanged(0, contacts.size)
Note
notifyItemXxx()系列方法比notifyDataSetChanged()好得多:它们只更新变化的项,还能触发默认的增删动画。但你要自己维护 position,容易出错。下一章的ListAdapter自动处理这些。
setHasFixedSize
如果列表大小不随内容变化(固定高度的 RecyclerView),设这个标志能优化性能:
recyclerView.setHasFixedSize(true)
Warning
setHasFixedSize(true)只在 RecyclerView 的高度不依赖内容时用。如果列表内容会改变 RecyclerView 自身的高度(比如嵌在 ScrollView 里),别设 true。
小结
RecyclerView 是传统 View 体系的高性能列表方案。三大组成:Adapter 绑数据、ViewHolder 缓存控件、LayoutManager 排列布局。用 notifyItemXxx() 精准更新而非 notifyDataSetChanged()。在 Compose 项目里,列表直接用 LazyColumn,但理解 RecyclerView 原理对维护老代码和用 AndroidView 嵌入有帮助。下一节学习 RecyclerView 进阶。