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 本章知识结构图

为了让你更直观地理解数据源到列表渲染的整个流程,我画了一张图:

数据源 → Adapter → RecyclerView 渲染流程 数据源 网络 / 数据库 / 本地 → List<User> Adapter 接收 List<User> onBindViewHolder() RecyclerView 渲染每个 Item 显示在屏幕上 关键要点: 1. 数据源必须转换为 List<T> 格式 2. Adapter 通过 updateData() 接收新数据 3. onBindViewHolder() 负责数据到视图的映射 4. 数据变化时使用对应的 notify 方法刷新

6.7 小结

这一章我们做了三件事:

  • 准备了数据源 —— 用 listOf() 创建不可变 List
  • 传递给了 Adapter —— 通过构造函数或 updateData() 方法
  • 绑定了视图 —— 在 onBindViewHolder() 里把数据填到控件上

说白了,数据源就是 Adapter 的「燃料」。没有数据,RecyclerView 就是个空壳。有了数据,它才能跑起来。下一章我们会深入 Adapter 的内部机制,看看它到底是怎么管理这些数据的。

一句话总结:数据源准备好 List,Adapter 拿到 List,onBindViewHolder 把 List 里的每一项映射到视图。三步走,缺一不可。


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