RecycleView系列四:局部刷新

275 阅读5分钟

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):判断是否是同一个ItemareContentsTheSame(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局部刷新原理

计算差异

  1. 数据集大小比较:通过getOldListSize()getNewListSize()获取新旧数据集的大小。
  2. 数据项同一性判断areItemsTheSame()方法用于判断两个数据项是否代表同一对象。
  3. 数据项内容差异判断:如果两个数据项是同一对象,则通过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 的内部逻辑来适应这种模式。