AsyncListDiffer 全面解析:高效更新 RecyclerView 数据的利器
1、AsyncListDiffer 是什么
AsyncListDiffer 是 Android Jetpack 提供的一个工具类,专门用于处理 RecyclerView 数据集的异步差异计算。它能够在后台线程中高效地计算新旧数据集的差异,并以最优方式更新 UI,避免了手动调用 notifyDataSetChanged() 带来的性能问题和动画缺失。
核心优势:
- 异步计算差异,不阻塞主线程
- 自动应用最优的更新策略(增删改移动画)
- 内置线程安全机制
- 简化代码,减少样板代码
2、基本使用方法
2.1、添加依赖
在 build.gradle 中添加 RecyclerView 依赖:
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2'
}
2.2、创建 DiffUtil.ItemCallback
定义如何比较数据项的差异:
class UserDiffCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
// 判断是否是同一个对象(通常比较唯一标识符)
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
// 判断内容是否相同
return oldItem == newItem
}
}
2.3、在 Adapter 中使用 AsyncListDiffer
class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
private val differ = AsyncListDiffer(this, UserDiffCallback())
// 对外暴露的提交数据方法
fun submitList(list: List<User>?) {
differ.submitList(list)
}
// 获取当前数据
fun getItem(position: Int): User {
return differ.currentList[position]
}
override fun getItemCount(): Int = differ.currentList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(user: User) {
itemView.findViewById<TextView>(R.id.tvName).text = user.name
itemView.findViewById<TextView>(R.id.tvAge).text = user.age.toString()
}
}
}
2.4、Activity/Fragment 中使用
class MainActivity : AppCompatActivity() {
private lateinit var adapter: UserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = UserAdapter()
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
// 加载数据
loadData()
}
private fun loadData() {
val users = listOf(
User(1, "张三", 25),
User(2, "李四", 30),
User(3, "王五", 28)
)
adapter.submitList(users)
}
}
3、高级用法
3.1、监听差异计算完成回调
adapter.submitList(newList) {
// 差异计算和 UI 更新完成后的回调
Log.d("TAG", "列表更新完成")
}
3.2、配合 ListAdapter 使用(推荐)
ListAdapter 是对 AsyncListDiffer 的进一步封装,使用更简洁:
class UserListAdapter : ListAdapter<User, UserListAdapter.ViewHolder>(UserDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(user: User) {
itemView.findViewById<TextView>(R.id.tvName).text = user.name
}
}
}
// 使用方式相同
adapter.submitList(newList)
3.3、处理部分更新(Payload)
当只有部分内容变化时,可以实现 payload 机制实现局部刷新:
class UserDiffCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
override fun getChangePayload(oldItem: User, newItem: User): Any? {
// 返回变化的内容标识
val payload = Bundle()
if (oldItem.name != newItem.name) {
payload.putString("name", newItem.name)
}
if (oldItem.age != newItem.age) {
payload.putInt("age", newItem.age)
}
return if (payload.size() > 0) payload else null
}
}
// Adapter 中处理 payload
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
return
}
val bundle = payloads[0] as Bundle
val user = getItem(position)
if (bundle.containsKey("name")) {
holder.nameTextView.text = bundle.getString("name")
}
if (bundle.containsKey("age")) {
holder.ageTextView.text = bundle.getInt("age").toString()
}
}
3.4、自定义 AsyncDifferConfig
可以自定义后台线程池等配置:
val config = AsyncDifferConfig.Builder<User>(UserDiffCallback())
.setBackgroundThreadExecutor(Executors.newSingleThreadExecutor())
.build()
val differ = AsyncListDiffer(this, config)
4、最佳实践与注意事项
4.1、数据类必须使用不可变对象
// 正确:使用 val 定义属性
data class User(
val id: Int,
val name: String,
val age: Int
)
// 错误:使用 var 可能导致数据不一致
data class User(
var id: Int,
var name: String,
var age: Int
)
4.2、提交新列表而非修改原列表
// 正确:创建新列表
val newList = ArrayList(adapter.currentList)
newList.add(newItem)
adapter.submitList(newList)
// 错误:直接修改原列表
adapter.currentList.add(newItem) // 不会触发更新
adapter.submitList(adapter.currentList) // 新旧列表相同,不会更新
4.3、处理空列表和加载状态
fun submitList(list: List<User>?) {
if (list == null) {
// 显示空状态
showEmptyView()
} else {
hideEmptyView()
adapter.submitList(list)
}
}
4.4、避免频繁提交小量更新
// 不推荐:频繁提交
for (item in items) {
val newList = ArrayList(adapter.currentList)
newList.add(item)
adapter.submitList(newList)
}
// 推荐:批量提交
val newList = ArrayList(adapter.currentList)
newList.addAll(items)
adapter.submitList(newList)
4.5、配合 SwipeRefreshLayout 使用
swipeRefreshLayout.setOnRefreshListener {
viewModel.loadData()
}
viewModel.users.observe(this) { users ->
adapter.submitList(users) {
// 更新完成后停止刷新动画
swipeRefreshLayout.isRefreshing = false
}
}
5、总结
AsyncListDiffer 是处理 RecyclerView 数据更新的最佳实践方案,它能够:
1、自动在后台线程计算数据差异,避免主线程卡顿
2、智能选择最优的更新动画(插入、删除、移动、修改)
3、简化 Adapter 代码,减少手动调用 notify 方法
4、配合 ListAdapter 使用,代码更加简洁
使用建议:
- 新项目直接使用 ListAdapter
- 旧项目逐步迁移到 AsyncListDiffer
- 始终使用不可变数据类
- 避免直接修改列表,始终创建新列表提交
- 合理使用 payload 实现局部刷新优化性能