1、局部刷新api
RecyclerView为了应对局部刷新提供了下面几个方法:
notifyItemChanged(int position):当某个位置的数据项发生变化时调用此方法。
notifyItemInserted(int position):当在指定位置插入了一个新数据项时调用此方法。
notifyItemRemoved(int position):当从指定位置删除了一个数据项时调用此方法。
notifyItemRangeChanged(int positionStart, int itemCount):当指定范围内的数据项发生变化时调用此方法。
notifyItemRangeInserted(int positionStart, int itemCount):当在指定位置插入了一个数据项范围时调用此方法。
notifyItemRangeRemoved(int positionStart, int itemCount):当从指定位置删除了一个数据项范围时调用此方法。
notifyDataSetChanged():当数据源发生不可预知的变化时调用此方法。注意,这个方法会刷新整个RecyclerView
notifyRange**系列方法刷新数据集的改动,还有notifyDataSetChanged():不触发Item动画、刷新整个可见区域。 除此之前,DiffUtil类更方便高效地处理数据差异。
2、DiffUtil实现局部刷新:高效处理新旧数据差异的方法。
使用DiffUtil的话只需要关注两个类:
(1)DiffUtil.Callback
我们需要通过这个类实现我们的diff规则。它有四个方法:
getOldListSize(int position):旧数据集的长度。
getNewListSize(int position):新数据集的长度。
areItemsTheSame(int position):判断是否是同一个Item。
areContentsTheSame(int position):如果是通一个Item,判断Item 的内容是否相同。
下面简单的介绍一下方法3和方法4,
- areItemsTheSame判断是否是同一个item,一般使用item数据中的唯一id来判断,例如数据库中的id。
- areContentsTheSame 是在方法3返回true的时候才会被调用。要解决的问题是item中某些字段内容的刷新。
例如朋友圈中更新点赞的状态。其实是不需要对整个item进行刷新的,只需要点赞所对应的控件进行刷新。
(2)DiffUtil.DiffResult
保存了通过 DiffUtil.Callback 计算出来,两个数据集的差异。 它是可以直接使用在 RecyclerView 上的。如果有必要,也是可以通过实现 ListUpdateCallback 接口,来比对这些差异。
3、DiffUtil局部刷新原理
计算差异
- 数据集大小比较:通过
getOldListSize()和getNewListSize()获取新旧数据集的大小。 - 数据项同一性判断:
areItemsTheSame()方法用于判断两个数据项是否代表同一对象。 - 数据项内容差异判断:如果两个数据项是同一对象,则通过
areContentsTheSame()进一步判断它们的内容是否相同。
基于这些比较,DiffUtil计算出从旧数据集到新数据集所需的最小更新操作集,包括插入、删除、移动和更新等操作。
应用差异
计算出差异后,DiffUtil会生成一个DiffResult对象,并通过调用dispatchUpdatesTo()方法将更新操作应用到RecyclerView的Adapter上。
这些更新操作会触发RecyclerView的局部刷新,例如通过调用Adapter的notify*方法来实现。
使用示例
下面是一个使用 DiffUtil 的基本示例,我们将通过创建一个简单的字符串列表的更新来展示如何使用 DiffUtil。
1. 定义列表项的数据模型
首先,定义一个简单的数据模型类,这里我们用一个简单的字符串作为列表项。
public class MyItem {
public final String id; // 假设这是唯一标识符
public final String content;
public MyItem(String id, String content) {
this.id = id;
this.content = content;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyItem myItem = (MyItem) o;
return id.equals(myItem.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}
2. 创建 DiffUtil.Callback
接下来,我们需要创建一个 DiffUtil.Callback 的实现,这个实现将告诉 DiffUtil 如何比较两个列表项。
import android.support.v7.util.DiffUtil;
public class MyDiffCallback extends DiffUtil.Callback {
private List<MyItem> oldList;
private List<MyItem> newList;
public MyDiffCallback(List<MyItem> oldList, List<MyItem> newList) {
this.oldList = oldList;
this.newList = newList;
}
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
// 使用ID判断两个item是否相同
return oldList.get(oldItemPosition).id.equals(newList.get(newItemPosition).id);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
// 这里可以进一步判断内容是否相同,如果不相同也需要更新
return oldList.get(oldItemPosition).content.equals(newList.get(newItemPosition).content);
}
}
3. 使用 DiffUtil 计算差异并更新 RecyclerView
最后,在你的 Activity 或 Fragment 中,当列表数据发生变化时,使用 DiffUtil 来计算差异,并更新 RecyclerView。
List<MyItem> oldList = ...; // 旧的列表数据
List<MyItem> newList = ...; // 新的列表数据
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new MyDiffCallback(oldList, newList));
// 在主线程中更新RecyclerView
runOnUiThread(() -> {
recyclerViewAdapter.submitList(newList); // 假设你的Adapter有submitList方法
result.dispatchUpdatesTo(recyclerViewAdapter); // 将差异应用到Adapter
});
注意:上面的 submitList 方法并不是 RecyclerView.Adapter 的一部分,而是一个常见的模式,用于在 RecyclerView.Adapter 中管理列表数据更新。你可能需要在你的 Adapter 类中实现这个方法,以便它能够接收新的列表数据并使用 DiffUtil 的结果来更新视图。
public void submitList(List<MyItem> newList) {
this.myItemList = newList;
notifyDataSetChanged(); // 或者使用notifyItemRangeChanged等更细粒度的更新方法
}
但是,如果你想要利用 DiffUtil 的结果来最小化更新,你应该在 submitList 方法内部或之后立即调用 result.dispatchUpdatesTo(this)(假设你的 Adapter 实现了 ListUpdateCallback 接口,这是 DiffUtil.Callback 的一个子类,用于接收更新回调)。然而,通常更简单的做法是让 Adapter 管理自己的数据,并在提交新数据时自动计算差异(使用 DiffUtil),然后仅更新有变化的部分。这通常意味着你需要自定义 Adapter 的内部逻辑来适应这种模式。