6、数据源与列表渲染:准备 List 数据,将数据传递给 Adapter
好,咱们接着往下走。上一章我们把 Adapter 的架子搭起来了,但那时候数据还是硬编码的,说白了就是「假数据」。这一章我们要解决一个核心问题:真实的数据从哪来?怎么喂给 Adapter?
我个人习惯把这一步叫做「数据源对接」。你想想看,RecyclerView 再牛,它也只是个展示工具。没有数据,它就是一张白纸。所以这一章,咱们就把数据源这件事彻底讲透。
6.1 数据源长什么样?
在 Android 开发里,数据源可以是很多形式:
- 本地写死的 List(开发阶段常用)
- 从网络 API 拿到的 JSON 数据
- 从 Room 数据库查出来的数据
- 甚至是从 ContentProvider 读取的联系人列表
但不管来源是什么,最终到了 Adapter 手里,它都得是一个 List<T>。嗯,这里要注意:这个 List 的类型必须和你 Adapter 的泛型参数一致。比如你 Adapter 写的是 MyAdapter<User>,那数据源就得是 List<User>。
核心原则:Adapter 只认 List,不关心数据从哪来。你给它什么 List,它就渲染什么界面。
6.2 准备一份真实的数据
我在项目中遇到过很多新手,直接在 Activity 里写死一个 ArrayList,然后传给 Adapter。这种做法在 Demo 里没问题,但到了真实项目里,数据往往是异步加载的。所以我们要提前养成好习惯:把数据准备和 Adapter 绑定分开。
来看代码。假设我们有一个 User 数据类:
data class User(
val id: Int,
val name: String,
val avatarUrl: String,
val isVip: Boolean
)
然后我们在 Activity 或者 Fragment 里准备数据:
// 模拟数据源 —— 实际项目里这里可能是网络请求回调
private fun loadUserList(): List<User> {
return listOf(
User(1, "张三", "https://example.com/avatar1.png", true),
User(2, "李四", "https://example.com/avatar2.png", false),
User(3, "王五", "https://example.com/avatar3.png", true),
User(4, "赵六", "https://example.com/avatar4.png", false)
)
}
你看,这里我用了 listOf() 而不是 ArrayList。为什么?因为 listOf() 返回的是不可变的 List,更安全。你想想看,如果数据在多个地方被修改,排查起来多头疼。
我的建议:在 Adapter 内部永远使用不可变 List。如果数据需要更新,直接替换整个 List 引用,而不是在原有 List 上做增删改。这样能避免很多诡异的并发问题。
6.3 把数据传递给 Adapter
数据准备好了,接下来就是「喂」给 Adapter。这一步其实就一行代码:
val userList = loadUserList()
val adapter = UserAdapter(userList)
recyclerView.adapter = adapter
但这里有个细节,很多人会忽略:Adapter 拿到数据后,应该立即触发一次刷新。如果你在 Adapter 的构造函数里直接赋值,那没问题。但如果你是用 setter 方法赋值,记得调用 notifyDataSetChanged()。
我习惯在 Adapter 里提供一个更新数据的方法:
class UserAdapter(private var userList: List<User>) : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
fun updateData(newList: List<User>) {
userList = newList
notifyDataSetChanged()
}
// ... 其他代码
}
这样,当网络请求回来时,你只需要调用 adapter.updateData(newList),界面就会自动刷新。嗯,这里要注意:notifyDataSetChanged() 会刷新所有可见项,如果数据量很大,可能会有性能问题。不过对于大多数场景,它已经够用了。
6.4 数据与视图的绑定逻辑
数据传进去了,Adapter 怎么知道每个位置该显示什么?答案就在 onBindViewHolder() 方法里。我们来看完整的绑定逻辑:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val user = userList[position]
holder.nameTextView.text = user.name
holder.vipBadge.visibility = if (user.isVip) View.VISIBLE else View.GONE
// 加载头像(这里用 Glide 举例)
Glide.with(holder.itemView.context)
.load(user.avatarUrl)
.into(holder.avatarImageView)
}
说白了,onBindViewHolder 就是「数据到视图」的映射器。你给什么数据,它就怎么渲染。我曾经犯过一个错误:在 onBindViewHolder 里做了耗时操作,比如网络请求。结果列表滑动起来卡得要命。后来才明白,onBindViewHolder 必须轻量,只做 UI 绑定。
避坑指南:千万不要在 onBindViewHolder 里做数据库查询、网络请求、文件读写等耗时操作。这些操作会阻塞主线程,导致列表卡顿。如果你需要异步加载图片,使用 Glide、Coil 等成熟的图片加载库,它们内部已经做了异步和缓存。
6.5 数据源变化时的处理策略
实际项目中,数据源不是一成不变的。用户可能下拉刷新,也可能加载更多。这时候怎么处理?
我总结了几种常见场景:
| 场景 | 操作 | 注意事项 |
|---|---|---|
| 下拉刷新 | 替换整个 List,调用 updateData() | 记得清空旧数据,避免内存泄漏 |
| 加载更多 | 在原有 List 末尾追加,调用 notifyItemRangeInserted() | 不要用 notifyDataSetChanged(),会丢失滚动位置 |
| 删除某项 | 从 List 中移除,调用 notifyItemRemoved() | 同时更新 List 引用,否则索引会错乱 |
| 修改某项 | 更新 List 中对应项,调用 notifyItemChanged() | 只刷新那一项,性能最优 |
你看,不同的操作对应不同的刷新方法。用对了,性能好;用错了,要么卡顿,要么闪屏。我个人建议:能用具体位置刷新的,就别用全局刷新。
6.6 本章知识结构图
为了让你更直观地理解数据源到列表渲染的整个流程,我画了一张图:
6.7 小结
这一章我们做了三件事:
- 准备了数据源 —— 用
listOf()创建不可变 List - 传递给了 Adapter —— 通过构造函数或 updateData() 方法
- 绑定了视图 —— 在 onBindViewHolder() 里把数据填到控件上
说白了,数据源就是 Adapter 的「燃料」。没有数据,RecyclerView 就是个空壳。有了数据,它才能跑起来。下一章我们会深入 Adapter 的内部机制,看看它到底是怎么管理这些数据的。
一句话总结:数据源准备好 List,Adapter 拿到 List,onBindViewHolder 把 List 里的每一项映射到视图。三步走,缺一不可。
公众号:蓝海资料掘金营,微信deep3321