RecyclerView:让列表更新可证明 · AndroidX 源码指南
AAndroidX 源码指南
View 体系
View 体系 · androidx.core

RecyclerView:让列表更新可证明Core 1.20.0-alpha01

用 ListAdapter 与 DiffUtil 区分条目身份和内容变化,管理 LayoutManager 与状态。

最后更新 2026-08-01

复制即用:最小列表

object UserDiff : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(old: User, new: User) = old.id == new.id
    override fun areContentsTheSame(old: User, new: User) = old == new
}

class UserAdapter : ListAdapter<User, UserViewHolder>(UserDiff) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder =
        UserViewHolder.create(parent)

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
}

// 使用
val adapter = UserAdapter()
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = adapter

adapter.submitList(newUsers)   // 提交新列表,异步 diff

DiffUtil:身份 vs 内容

object UserDiff : DiffUtil.ItemCallback<User>() {
    // 同一业务实体吗?(id 相同 = 同一个人)
    override fun areItemsTheSame(old: User, new: User) = old.id == new.id

    // 显示内容变了吗?(数据类 == 全字段比较)
    override fun areContentsTheSame(old: User, new: User) = old == new
}

关键

  • areItemsTheSame = 身份(决定哪些条目”没动”)。
  • areContentsTheSame = 内容(决定”动了”的条目是否重绘)。
  • 两者都写成引用比较old === new)会让原地修改的列表无法正确刷新。

ListAdapter 与 AsyncListDiffer

// ListAdapter:Adapter + AsyncListDiffer 封装
class UserAdapter : ListAdapter<User, UserViewHolder>(UserDiff)

// AsyncListDiffer:底层机制(自定义 Adapter 时用)
class UserAdapter : RecyclerView.Adapter<UserViewHolder>() {
    private val differ = AsyncListDiffer(this, UserDiff)

    override fun getItemCount() = differ.currentList.size

    fun submitList(list: List<User>) = differ.submitList(list)
}

规则:每次提交新的不可变列表;不要修改已提交的 list 或影响 UI 的对象后,再提交同一个实例。

LayoutManager:排列方式

// 线性
recyclerView.layoutManager = LinearLayoutManager(context)

// 网格
recyclerView.layoutManager = GridLayoutManager(context, 2)

// 瀑布流
recyclerView.layoutManager = StaggeredGridLayoutManager(2, VERTICAL)
LayoutManager场景
LinearLayoutManager单列列表
GridLayoutManager规则网格
StaggeredGridLayoutManager瀑布流(高度不一)

三类状态要分开保存

状态存放
业务数据ViewModel / Repository
滚动位置layoutManager.onSaveInstanceState()
选择状态单独保存(不塞 ViewHolder)

ViewHolder 是复用的:滚动时反复绑定不同条目,不能在其中保存”选中/滚动”这类持久状态。

常见陷阱

  • Diff 写引用比较:原地修改列表刷新不了。
  • 复用已提交的 list:改原 list 再 submit 同实例,diff 失效。
  • 忘 LayoutManager:不设置列表空白/崩溃。
  • ViewHolder 存状态:滚动后状态串到别的条目。
  • 大数据用全量 notifyDataSetChanged:应 submitList 走 diff。

复制即用

class UserViewHolder(
    private val binding: ItemUserBinding,
) : RecyclerView.ViewHolder(binding.root) {

    fun bind(user: User, onClick: (User) -> Unit) {
        binding.name.text = user.name
        binding.root.setOnClickListener { onClick(user) }
    }

    companion object {
        fun create(parent: ViewGroup): UserViewHolder {
            val binding = ItemUserBinding.inflate(
                LayoutInflater.from(parent.context), parent, false
            )
            return UserViewHolder(binding)
        }
    }
}

// Fragment 中使用
class UserListFragment : Fragment() {
    private val adapter = UserAdapter()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recyclerView.layoutManager = LinearLayoutManager(requireContext())
        recyclerView.adapter = adapter

        viewModel.users.observe(viewLifecycleOwner) { users ->
            adapter.submitList(users)
        }
    }
}

要点

  • areItemsTheSame 管身份、areContentsTheSame 管内容。
  • ListAdapter 内部 AsyncListDiffer 异步 diff;提交不可变新列表。
  • LayoutManager 决定排列;滚动/选择/数据三类状态分开存。
  • ViewHolder 复用,不存持久状态。

相关页面