【Android】RecyclerView 刷新方式全解析:从 notifyDataSetChanged 到 DiffUtil

文章目录

  • [【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。

需要重载带 payloadonBindViewHolder

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 两个列表如何比较。其中有以下五个方法:

  1. int getOldListSize()

    作用:返回旧列表长度。

  2. int getNewListSize()

    作用:返回新列表长度。

  3. boolean areItemsTheSame(int oldItemPosition, int newItemPosition)

    作用 :判断旧列表中位置 oldItemPosition 的项和新列表中位置 newItemPosition 的项 是否代表同一个实体

    常用做法:比较唯一标识符,如ID。

    返回 :如果返回 true,说明是"同一个 item",接下来会调用 areContentsTheSame() 检查内容是否变化;如果返回 false,会被认为是不同的 item(insert/remove)。

  4. boolean areContentsTheSame(int oldItemPosition, int newItemPosition)

    作用 :在 areItemsTheSame() 返回 true 的前提下,判断内容是否相等(是否需要刷新 UI)。

    常用做法 :比较字段或调用 equals()(但要确保 equals 的语义是"内容相等")。

    返回true 表示内容相同(不需要刷新),false 表示内容不同(会触发 notifyItemChanged)。

  5. @Nullable Object getChangePayload(int oldItemPosition, int newItemPosition)(可选)

    作用 :当 areItemsTheSame() 返回 trueareContentsTheSame() 返回 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.CallbackareItemsTheSame / 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.AdapterListUpdateCallback),最终触发 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)。

推荐做法

  1. 在后台或同步地调用 calculateDiff 得到 diffResult
  2. 主线程 (UI 线程)先替换 Adapter 的 backing dataadapter.data = newListadapter.setData(newList))。
  3. 立即调用 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 等)来更新列表。

使用步骤

  1. 创建一个 DiffUtil.ItemCallback 的实现,用于定义如何比较列表中的项目。
  2. 在适配器中创建一个 AsyncListDiffer 实例。
  3. 使用 AsyncListDiffer 的 submitList 方法来更新数据。
  4. 在适配器的方法中,通过 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);

这里面有几个方法需要介绍一下:

  1. differ.getCurrentList()(返回 List)
    • 作用 :返回当前已"提交并应用"的不可变快照(List<T>)。
    • 特点 :安全在主线程读取,返回的是内部快照引用,不应修改;getCurrentList().size() 可用于 getItemCount()
  2. differ.getItem(int position)
    • 作用 :返回 getCurrentList().get(position) 的元素,便于在 onBindViewHolder 中取数据。
    • 注意getItem 是只读视图。
  3. 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 方法来更新数据即可。

使用步骤

  1. 创建一个 DiffUtil.ItemCallback 的实现(同上)。
  2. 继承 ListAdapter,并传入 ItemCallback。
  3. 实现 onCreateViewHolder 和 onBindViewHolder。
  4. 使用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) :包括notifyItemChangednotifyItemInsertednotifyItemRemovednotifyItemMoved以及对应的范围方法。这些方法针对特定位置或范围进行更新,有动画效果,效率较高。适用于明确知道变化位置的情况。
  • DiffUtil:智能计算新旧列表差异,并自动调用合适的局部刷新方法。效率高,有动画效果,但需要实现 DiffUtil.Callback。适用于复杂的数据变化,特别是从后台获取新数据并更新列表时。
  • AsyncListDiffer:封装了DiffUtil和后台线程计算,简化了使用方式。需要在 Adapter 中创建 AsyncListDiffer 实例,并实现 ItemCallback。
  • ListAdapter:Google 官方推荐的 Adapter 基类,内部使用 AsyncListDiffer,让开发者更专注于 ViewHolder 和绑定逻辑。适用于大多数场景,特别是新项目。
  • Payload:配合使用,允许局部更新ItemView中的特定部分,避免重新绑定整个ItemView,进一步提升性能。

如何选择?

  • 对于简单的列表,数据量小,变化不频繁,可以使用notifyDataSetChanged()或局部刷新方法。
  • 对于数据源复杂,频繁更新,且追求高性能和动画效果,推荐使用DiffUtil或其封装类(AsyncListDifferListAdapter)。
  • 在新项目中,建议直接使用ListAdapter,它已经封装了最佳实践。
  • 当列表项视图复杂,且只有部分内容需要更新时,使用Payload进行局部更新。

最后,感谢阅读!如果你有任何问题或建议,欢迎在评论区留言讨论。

相关推荐
Ray Liang1 小时前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
Java水解1 小时前
Java 中间件:Dubbo 服务降级(Mock 机制)
java·后端
砖厂小工3 小时前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心4 小时前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心4 小时前
Android 17 来了!新特性介绍与适配建议
android·前端
SimonKing5 小时前
OpenCode AI辅助编程,不一样的编程思路,不写一行代码
java·后端·程序员
FastBean6 小时前
Jackson View Extension Spring Boot Starter
java·后端
Kapaseker6 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴7 小时前
Android17 为什么重写 MessageQueue
android
Seven977 小时前
剑指offer-79、最⻓不含重复字符的⼦字符串
java