文章目录
- [【Android】RecyclerView 刷新方式全解析:从 notifyDataSetChanged 到 DiffUtil](#【Android】RecyclerView 刷新方式全解析:从 notifyDataSetChanged 到 DiffUtil)
-
- [1. notifyDataSetChanged() ------ 全量刷新](#1. notifyDataSetChanged() —— 全量刷新)
- [2. notifyItemXXX 系列 ------ 局部刷新](#2. notifyItemXXX 系列 —— 局部刷新)
-
- [2.1 notifyItemChanged(int position) ------ 刷新单个Item](#2.1 notifyItemChanged(int position) —— 刷新单个Item)
- [2.2 notifyItemChanged(int position, Object payload) ------ 局部刷新](#2.2 notifyItemChanged(int position, Object payload) —— 局部刷新)
- [2.3 notifyItemInserted(int position) ------ 插入单个Item](#2.3 notifyItemInserted(int position) —— 插入单个Item)
- [2.4 notifyItemRemoved(int position) ------ 删除单个Item](#2.4 notifyItemRemoved(int position) —— 删除单个Item)
- [2.5 notifyItemMoved(int fromPosition, int toPosition) ------ 移动Item位置(拖拽排序)](#2.5 notifyItemMoved(int fromPosition, int toPosition) —— 移动Item位置(拖拽排序))
- [2.6 notifyItemRangeChanged(int positionStart, int itemCount) ------ 批量更新Item](#2.6 notifyItemRangeChanged(int positionStart, int itemCount) —— 批量更新Item)
- [2.7 notifyItemRangeInserted(int positionStart, int itemCount) ------ 批量插入Item](#2.7 notifyItemRangeInserted(int positionStart, int itemCount) —— 批量插入Item)
- [2.8 notifyItemRangeRemoved(int positionStart, int itemCount) ------ 批量删除Item](#2.8 notifyItemRangeRemoved(int positionStart, int itemCount) —— 批量删除Item)
- [3. DiffUtil ------ 自动计算差异,刷新最小项](#3. DiffUtil —— 自动计算差异,刷新最小项)
-
- [3.1 基本使用方式](#3.1 基本使用方式)
-
- [3.1.1 实现DiffUtil.Callback](#3.1.1 实现DiffUtil.Callback)
- [3.1.2 调用 DiffUtil.calculateDiff 计算差异结果](#3.1.2 调用 DiffUtil.calculateDiff 计算差异结果)
- [3.1.3 DiffResult.dispatchUpdatesTo 刷新](#3.1.3 DiffResult.dispatchUpdatesTo 刷新)
- [3.2 在后台线程执行计算](#3.2 在后台线程执行计算)
- [3.3 AsyncListDiffer 和 ListAdapter](#3.3 AsyncListDiffer 和 ListAdapter)
-
- [3.3.1 AsyncListDiffer](#3.3.1 AsyncListDiffer)
- [3.3.2 ListAdapter](#3.3.2 ListAdapter)
- 注意事项
- 总结
【Android】RecyclerView 刷新方式全解析:从 notifyDataSetChanged 到 DiffUtil
RecyclerView 是 Android 开发中最常用的 UI 组件之一,而"如何刷新列表"是每个开发者必然会遇到的问题。
刚开始学 Android 时,只会用:
java
adapter.notifyDataSetChanged();
简单粗暴,能解决大部分需求,但是这种方式缺点很多,真正项目中我们更需要性能更好、动画更自然、刷新粒度更精准的刷新方式。
本文将从最基础的 notifyDataSetChanged() 开始,介绍 RecyclerView 所有刷新方式。
1. notifyDataSetChanged() ------ 全量刷新
这应该是每个新手最先掌握的一种刷新方式,使用方法很简单:
java
adapter.notifyDataSetChanged();
调用后,通知 RecyclerView 整个数据集已变化,强制重新执行所有 item 的 onBindViewHolder,不计算差异、不做局部优化,会导致全量刷新。
优点:这种方式优点非常明显,简单易用,一行代码搞定,不需要复杂逻辑。
缺点:性能最差,所有 item 都会重新绑定、重绘;无动画效果;不能指定变化位置;可能导致界面闪烁。
适用场景:数据结构完全变化,旧数据与全新数据无任何关联;初始加载;调试、临时代码使用;数据量很小(<10)。
能不用就不用。它是最笨的方法,但也最不推荐。
2. notifyItemXXX 系列 ------ 局部刷新
RecyclerView 提供了很多局部刷新方法,只让"变化的部分"刷新。
2.1 notifyItemChanged(int position) ------ 刷新单个Item
只刷新指定 position 对应 item。
java
adapter.notifyItemChanged(position);
优点:刷新单个 item,性能好一些。
缺点:默认仍会重绘整个 item,常出现轻微闪烁。
2.2 notifyItemChanged(int position, Object payload) ------ 局部刷新
只刷新 item 内部的某个控件,不会重绘整个 item。
需要重载带 payload 的 onBindViewHolder:
java
@Override
public void onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads) {
if (!payloads.isEmpty()) {
// 局部刷新:处理 payloads(可包含多个)
for (Object payload : payloads) {
if (payload instanceof String) {
String key = (String) payload;
if ("like".equals(key)) {
// 只更新 like 字段
User user = data.get(position);
holder.tvLike.setText(String.valueOf(user.likeCount));
return; // 局部刷新后直接返回,避免完整绑定
}
} else if (payload instanceof Bundle) {
// 如果你用 Bundle 传多个字段变化,可以解析 Bundle
}
// 其它 payload 处理...
}
}
// payloads 为空时回退到完整绑定(或直接调用 super)
super.onBindViewHolder(holder, position, payloads);
}
使用也很简单:
java
data.get(pos).likeCount += 1;
// 传递简单标识(也可以传 Bundle/自定义对象)
adapter.notifyItemChanged(pos, "like");
2.3 notifyItemInserted(int position) ------ 插入单个Item
java
data.add(position, item);
adapter.notifyItemInserted(position);
2.4 notifyItemRemoved(int position) ------ 删除单个Item
java
data.remove(position);
adapter.notifyItemRemoved(position);
2.5 notifyItemMoved(int fromPosition, int toPosition) ------ 移动Item位置(拖拽排序)
java
Collections.swap(data, from, to);
adapter.notifyItemMoved(from, to);
2.6 notifyItemRangeChanged(int positionStart, int itemCount) ------ 批量更新Item
java
for (int i = 0; i < newItems.size(); i++) {
data.set(startPos + i, newItems.get(i));
}
adapter.notifyItemRangeChanged(startPos, newItems.size());
2.7 notifyItemRangeInserted(int positionStart, int itemCount) ------ 批量插入Item
java
int startPos = data.size();
data.addAll(moreItems);
adapter.notifyItemRangeInserted(startPos, moreItems.size());
2.8 notifyItemRangeRemoved(int positionStart, int itemCount) ------ 批量删除Item
java
data.subList(startPos, endPos).clear();
adapter.notifyItemRangeRemoved(startPos, endPos - startPos);
3. DiffUtil ------ 自动计算差异,刷新最小项
DiffUtil 是一个用于计算两个列表之间差异的实用工具类。它可以优化 RecyclerView 的刷新操作,仅刷新需要更新的部分,从而提高性能并减少不必要的操作。
3.1 基本使用方式
3.1.1 实现DiffUtil.Callback
首先需要创建一个类实现DiffUtil.Callback,用于告诉 DiffUtil 两个列表如何比较。其中有以下五个方法:
-
int getOldListSize()作用:返回旧列表长度。
-
int getNewListSize()作用:返回新列表长度。
-
boolean areItemsTheSame(int oldItemPosition, int newItemPosition)作用 :判断旧列表中位置
oldItemPosition的项和新列表中位置newItemPosition的项 是否代表同一个实体。常用做法:比较唯一标识符,如ID。
返回 :如果返回
true,说明是"同一个 item",接下来会调用areContentsTheSame()检查内容是否变化;如果返回false,会被认为是不同的 item(insert/remove)。 -
boolean areContentsTheSame(int oldItemPosition, int newItemPosition)作用 :在
areItemsTheSame()返回true的前提下,判断内容是否相等(是否需要刷新 UI)。常用做法 :比较字段或调用
equals()(但要确保 equals 的语义是"内容相等")。返回 :
true表示内容相同(不需要刷新),false表示内容不同(会触发notifyItemChanged)。 -
@Nullable Object getChangePayload(int oldItemPosition, int newItemPosition)(可选)作用 :当
areItemsTheSame()返回true且areContentsTheSame()返回false时,返回"变化的差量信息 "。该对象会作为payload传入 Adapter 的onBindViewHolder(holder, position, List<Object> payloads),以实现局部绑定(避免整条 item 重绑定)。返回 :返回
null等同于"没有 payload",RecyclerView 会做完整绑定。注意 :如果要使用该方法,需要在 Adapter 重写
onBindViewHolder(..., payloads)。
代码示例:
java
public class UserDiffCallback extends DiffUtil.Callback {
private final List<User> oldList;
private final List<User> newList;
public UserDiffCallback(List<User> oldList, List<User> newList) {
this.oldList = oldList;
this.newList = newList;
}
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
// 判断两个Item是否代表同一个对象
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
// 通常比较唯一标识符,如 ID
return oldList.get(oldItemPosition).getId() == newList.get(newItemPosition).getId();
}
// 判断两个Item的内容是否相同
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
User oldUser = oldList.get(oldItemPosition);
User newUser = newList.get(newItemPosition);
return oldUser.equals(newItem);//需重写User的equals()和hashCode()方法
// 也可以直接比较所有字段
/* return oldUser.getName().equals(newUser.getName()) &&
oldUser.getAge() == newUser.getAge() &&
oldUser.getAvatarUrl().equals(newUser.getAvatarUrl());*/
}
// 可选:当 areItemsTheSame 返回 true 但 areContentsTheSame 返回 false 时调用
// 用于局部更新,避免重新绑定整个 item
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
User oldUser = oldList.get(oldItemPosition);
User newUser = newList.get(newItemPosition);
Bundle diffBundle = new Bundle();
if (!oldUser.getName().equals(newUser.getName())) {
diffBundle.putString("name", newUser.getName());
}
if (oldUser.getAge() != newUser.getAge()) {
diffBundle.putInt("age", newUser.getAge());
}
if (!oldUser.getAvatarUrl().equals(newUser.getAvatarUrl())) {
diffBundle.putString("avatar", newUser.getAvatarUrl());
}
return diffBundle.size() == 0 ? null :diffBundle;
}
}
注意事项:
-
输入列表必须是快照 :在计算 diff 时不要修改 old/new 列表。最保险的做法是先
new ArrayList<>(old)和new ArrayList<>(newData)。 -
areItemsTheSame 一定要稳定且唯一:不稳定的 id 会导致错误的插入/删除/移动。
-
areContentsTheSame 要尽量高效:避免复杂计算,或提前计算 hash/code。(但要注意 equals 语义)
-
getChangePayload 返回轻量对象:不要返回大的集合或含 Context 的对象,推荐返回 Bundle 或简单 POJO。
3.1.2 调用 DiffUtil.calculateDiff 计算差异结果
使用你的 Callback 实例调用 DiffUtil.calculateDiff,它将计算旧列表和新列表之间的差异(通过你在 Callback 中的规则),返回一个 DiffResult(最小变更集)。
该方法有两个参数:
-
Callback基于你实现的
DiffUtil.Callback(areItemsTheSame/areContentsTheSame/ 可选getChangePayload)计算出把旧列表变成新列表所需的最小操作序列(增、删、改、移)。 -
detectMoves(可选)默认为
true,会尝试检测**元素的移动(move)**并把它作为move操作;检测 move 会额外消耗时间。设置为
false,则不检测 move,只用 insert/remove/changed,性能更好但没有移动动画。检测移动会消耗更多时间,如果你不关心 item 的移动动画,可设为
false以提升性能。
常用签名:
java
public static DiffUtil.DiffResult calculateDiff(DiffUtil.Callback cb);
public static DiffUtil.DiffResult calculateDiff(DiffUtil.Callback cb, boolean detectMoves);
注意事项 :尽量避免在主线程大规模计算 。大数据量时把 calculateDiff 放到后台线程或使用 AsyncListDiffer / ListAdapter(它们内部异步计算)。
3.1.3 DiffResult.dispatchUpdatesTo 刷新
使用diffResult.dispatchUpdatesTo(target)把变更集"应用"到目标 (通常是 RecyclerView.Adapter 或 ListUpdateCallback),最终触发 notifyXXX 调用与 RecyclerView 的动画/刷新。
该方法的作用是把 DiffResult 中的变更以正确顺序 分发给目标 ListUpdateCallback(或直接传 RecyclerView.Adapter),目标收到回调后通常会调用 notifyItemInserted/Removed/Changed/Moved,从而驱动 RecyclerView 做动画和局部更新。
常用签名:
java
public void dispatchUpdatesTo(RecyclerView.Adapter adapter);
public void dispatchUpdatesTo(ListUpdateCallback updateCallback);
- 如果传
RecyclerView.Adapter,内部会封装成AdapterListUpdateCallback(adapter)(它会把回调转换为adapter.notifyItemInserted(...)等)。 - 也可以传自定义的
ListUpdateCallback,用于手工控制如何应用变更(例如先更新 UI 数据结构,再调用 notify)。
推荐做法:
- 在后台或同步地调用
calculateDiff得到diffResult。 - 在 主线程 (UI 线程)先替换 Adapter 的 backing data (
adapter.data = newList或adapter.setData(newList))。 - 立即调用
diffResult.dispatchUpdatesTo(adapter)。
代码示例:
java
public void updateList(List<User> newList) {
// 计算差异
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
new UserDiffCallback(this.userList, newList)
);
// 更新数据源
this.userList.clear();
this.userList.addAll(newList);
// 分发更新
diffResult.dispatchUpdatesTo(this);
}
为什么先替换数据?
因为
dispatchUpdatesTo在回调(例如notifyItemChanged)时,RecyclerView 会调用 Adapter 的getItemCount()/getItem(position)等方法;若数据还没更新,会导致IndexOutOfBoundsException或显示旧数据。如果你不想先替换数据,可以 dispatch 到自定义
ListUpdateCallback,在回调里按你需要的顺序先更新数据结构再调用adapter.notifyXXX(但这样更易出错,普通场景不建议)。
注意 :dispatchUpdatesTo(...) 在调用线程上同步执行 (会立即触发对应的 notify... 调用)。因此,必须在主线程对 RecyclerView/Adapter 进行 dispatch(否则会抛异常或 UI 不安全)。
如果你想在每个回调前后做额外工作(例如更新其他数据结构、统计日志、或在 dispatch 前后禁用动画),可以自定义 ListUpdateCallback:
java
DiffUtil.DiffResult result = DiffUtil.calculateDiff(cb, true);
result.dispatchUpdatesTo(new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
// 先更新 backing list 或做统计
adapter.notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
adapter.notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
adapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
adapter.notifyItemRangeChanged(position, count, payload);
}
});
但负责手动同步数据和 dispatch 的复杂性较高,不建议滥用。
3.2 在后台线程执行计算
当数据量不大时,我们可以在UI线程中直接更新数据,数据量大时建议在后台线程执行计算,避免阻塞主线程。
代码示例:
java
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
DiffUtil.DiffResult diffResult =
DiffUtil.calculateDiff(new ItemDiffCallback(oldList, newList));
// 切回主线程
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
mData.clear();
mData.addAll(newList);
// 更新 RecyclerView
diffResult.dispatchUpdatesTo(adapter);
}
});
}
});
3.3 AsyncListDiffer 和 ListAdapter
ListAdapter 和 AsyncListDiffer 都是 Android 官方架构组件中为了简化 RecyclerView.Adapter 的数据更新和 DiffUtil 的使用而提供的工具。
它们都是基于 DiffUtil 的,但封装了后台计算差异和主线程更新的细节,让我们更容易使用。
3.3.1 AsyncListDiffer
AsyncListDiffer 是一个可以在 RecyclerView 适配器中使用的工具类,它负责持有当前列表的数据,并且当提交新列表时,会自动计算新旧列表的差异,然后通过 RecyclerView.Adapter 的更新方法(如 notifyItemInserted 等)来更新列表。
使用步骤:
- 创建一个 DiffUtil.ItemCallback 的实现,用于定义如何比较列表中的项目。
- 在适配器中创建一个 AsyncListDiffer 实例。
- 使用 AsyncListDiffer 的 submitList 方法来更新数据。
- 在适配器的方法中,通过 AsyncListDiffer 获取数据。
代码示例:
java
// 1. 数据模型
public class User {
private String id;
private String name;
private int age;
// 构造函数、getter、setter...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age &&
Objects.equals(id, user.id) &&
Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
}
// 2. 创建 DiffUtil.ItemCallback
public class UserDiffCallback extends DiffUtil.ItemCallback<User> {
@Override
public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) {
return oldItem.getId().equals(newItem.getId());
}
@Override
public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) {
return oldItem.equals(newItem);
}
// 可选:获取变更内容(用于局部更新)
// 需要在Adapter中处理payload
@Nullable
@Override
public Object getChangePayload(@NonNull User oldItem,
@NonNull User newItem) {
// ...
}
}
// 3. 在 Adapter 中使用 AsyncListDiffer
public class UserAdapter extends RecyclerView.Adapter<UserViewHolder> {
private final AsyncListDiffer<User> differ =
new AsyncListDiffer<>(this, new UserDiffCallback());
public void submitList(List<User> users) {
differ.submitList(users);
}
@Override
public int getItemCount() {
return differ.getCurrentList().size();
}
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new UserViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
User user = differ.getCurrentList().get(position);
holder.bind(user);
}
}
// 4. 使用示例
UserAdapter adapter = new UserAdapter();
recyclerView.setAdapter(adapter);
// 更新数据(自动计算差异)
List<User> newUsers = fetchUsersFromNetwork();
adapter.submitList(newUsers);
这里面有几个方法需要介绍一下:
differ.getCurrentList()(返回 List)- 作用 :返回当前已"提交并应用"的不可变快照(
List<T>)。 - 特点 :安全在主线程读取,返回的是内部快照引用,不应修改;
getCurrentList().size()可用于getItemCount()。
- 作用 :返回当前已"提交并应用"的不可变快照(
differ.getItem(int position)- 作用 :返回
getCurrentList().get(position)的元素,便于在onBindViewHolder中取数据。 - 注意 :
getItem是只读视图。
- 作用 :返回
differ.submitList(List<T> newList)- 作用 :提交新列表(可以为
null,表示清空列表)让AsyncListDiffer异步计算差异并把结果 dispatch 给ListUpdateCallback。 - 注意 :每次都应该提交一个新的 List 实例(副本),不要传递同一个对象。
submitList是线程安全的,内部会安排后台计算,但最好在主线程调用以避免边界情况(尽管 AsyncListDiffer 允许从任意线程提交)。 - 可选回调 :
void submitList(List<T> list, @Nullable Runnable commitCallback),commitCallback会在差异计算完成并且更新已分发到 Adapter/RecyclerView 后被调用(在主线程)。
- 作用 :提交新列表(可以为
工作原理:
AsyncListDiffer内部维护了一个当前列表(在后台线程中计算差异时,会拷贝当前列表作为旧列表)。当你调用 submitList 提交新列表时,它会启动一个后台任务(通过一个后台Executor)来执行DiffUtil.calculateDiff,然后根据计算结果,在主线程中通过RecyclerView.Adapter的更新方法来更新列表。
注意:AsyncListDiffer 要求 RecyclerView.Adapter 实现 ListUpdateCallback 接口(实际上 RecyclerView.Adapter 已经实现了这个接口),以便在计算得到差异后,调用相应的更新方法。
3.3.2 ListAdapter
ListAdapter 是 Google 官方提供的一个 RecyclerView.Adapter 的子类,它内部已经封装了 AsyncListDiffer,使得我们使用起来更加方便。我们只需要指定一个 DiffUtil.ItemCallback,然后通过 submitList 方法来更新数据即可。
使用步骤:
- 创建一个 DiffUtil.ItemCallback 的实现(同上)。
- 继承 ListAdapter,并传入 ItemCallback。
- 实现 onCreateViewHolder 和 onBindViewHolder。
- 使用submitList更新数据。
代码示例:
java
// 1. 直接继承 ListAdapter(无需手动创建 AsyncListDiffer)
public class UserListAdapter extends ListAdapter<User, UserViewHolder> {
// 2. 在构造函数中传入 DiffCallback
public UserListAdapter() {
super(new UserDiffCallback);
}
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_user, parent, false);
return new UserViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
User user = getItem(position); // ListAdapter 提供,内部调用differ
holder.bind(user);
}
}
// 3. 使用示例
UserListAdapter adapter = new UserListAdapter();
recyclerView.setAdapter(adapter);
// 提交新数据(自动后台计算差异)
List<User> users = fetchUsersFromNetwork();
adapter.submitList(users);
需要注意的是,ListAdapter 已经覆写并实现了 getItemCount()(它返回 getCurrentList().size())。不用重写 getItemCount(),也不要去维护自己的 data list。
工作原理:
ListAdapter 内部已经持有一个 AsyncListDiffer,并在构造时传入一个 DiffUtil.ItemCallback。ListAdapter 已经实现了 getItemCount() 和 getItem() 等方法,这些方法都是通过内部的 AsyncListDiffer 来获取数据。
当我们调用 submitList 方法时,实际上就是调用了内部 AsyncListDiffer 的 submitList 方法。
注意事项
- 为了确保 DiffUtil 能够正确工作,数据模型类应该正确实现 equals 和 hashCode 方法,或者在 ItemCallback 中正确实现 areContentsTheSame。
- AsyncListDiffer 和 ListAdapter 在比较列表时,会比较列表的引用。如果你提交了一个和当前列表相同(引用相同)的列表,那么不会触发计算。因此,每次更新数据时,应该创建一个新的列表对象,或者确保在修改列表后提交一个新列表。
- AsyncListDiffer 和 ListAdapter的submitList 方法可以连续调用,但只有最后一次提交的列表会被计算和更新。也就是说,如果连续提交多次,可能会跳过中间状态。
- 由于 DiffUtil 计算是在后台线程进行的,所以确保在计算期间,旧列表和新列表不会被修改。因此,在 ItemCallback 中,应该只读取列表中的数据,而不应该修改它们。
总结
刷新方式回顾
- notifyDataSetChanged():最基础的刷新方式,但效率最低,会重新绑定所有可见项,没有动画效果。适用于数据完全改变或列表很小的情况。
- 局部刷新方法(notifyItemXxx) :包括
notifyItemChanged、notifyItemInserted、notifyItemRemoved、notifyItemMoved以及对应的范围方法。这些方法针对特定位置或范围进行更新,有动画效果,效率较高。适用于明确知道变化位置的情况。 - DiffUtil:智能计算新旧列表差异,并自动调用合适的局部刷新方法。效率高,有动画效果,但需要实现 DiffUtil.Callback。适用于复杂的数据变化,特别是从后台获取新数据并更新列表时。
- AsyncListDiffer:封装了DiffUtil和后台线程计算,简化了使用方式。需要在 Adapter 中创建 AsyncListDiffer 实例,并实现 ItemCallback。
- ListAdapter:Google 官方推荐的 Adapter 基类,内部使用 AsyncListDiffer,让开发者更专注于 ViewHolder 和绑定逻辑。适用于大多数场景,特别是新项目。
- Payload:配合使用,允许局部更新ItemView中的特定部分,避免重新绑定整个ItemView,进一步提升性能。
如何选择?
- 对于简单的列表,数据量小,变化不频繁,可以使用
notifyDataSetChanged()或局部刷新方法。 - 对于数据源复杂,频繁更新,且追求高性能和动画效果,推荐使用
DiffUtil或其封装类(AsyncListDiffer、ListAdapter)。 - 在新项目中,建议直接使用
ListAdapter,它已经封装了最佳实践。 - 当列表项视图复杂,且只有部分内容需要更新时,使用
Payload进行局部更新。
最后,感谢阅读!如果你有任何问题或建议,欢迎在评论区留言讨论。