You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android:使用DiffUtil更新多视图类型的RecyclerView

嘿,你已经找对方向啦!针对多类型RecyclerView的DiffUtil实现,你写的areItemsTheSame逻辑完全没问题,咱们把剩下的部分补全,再聊聊这种场景下的小技巧。

第一步:优化类型安全(用基类代替Object)

为了避免大量instanceof的繁琐判断,先给所有Item类型定义一个基类,统一处理类型标识和唯一ID:

abstract class BaseRecyclerItem {
    // 返回当前Item的类型标识,比如0代表ViewType1,1代表ViewType2
    public abstract int getItemViewType();
    // 返回每个Item的唯一标识,用来判断是否是同一个Item
    public abstract String getUniqueId();
}

// 你的第一个视图类型
class ViewType1 extends BaseRecyclerItem {
    private String uniqueId;
    private String title;
    private String content;

    // 构造方法、getter、setter省略
    @Override
    public int getItemViewType() {
        return 0;
    }

    @Override
    public String getUniqueId() {
        return uniqueId;
    }

    // 重写equals方法,方便后续内容比较
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ViewType1 viewType1 = (ViewType1) o;
        return Objects.equals(title, viewType1.title) && Objects.equals(content, viewType1.content);
    }
}

// 你的第二个视图类型
class ViewType2 extends BaseRecyclerItem {
    private String uniqueId;
    private String imageUrl;
    private int count;

    // 构造方法、getter、setter省略
    @Override
    public int getItemViewType() {
        return 1;
    }

    @Override
    public String getUniqueId() {
        return uniqueId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ViewType2 viewType2 = (ViewType2) o;
        return count == viewType2.count && Objects.equals(imageUrl, viewType2.imageUrl);
    }
}

第二步:完善DiffUtil.Callback

基于基类来写DiffCallback,代码会更简洁且不易出错:

public class TestDiffCallback extends DiffUtil.Callback {
    private final List<BaseRecyclerItem> oldList;
    private final List<BaseRecyclerItem> newList;

    public TestDiffCallback(List<BaseRecyclerItem> oldList, List<BaseRecyclerItem> 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) {
        BaseRecyclerItem oldItem = oldList.get(oldItemPosition);
        BaseRecyclerItem newItem = newList.get(newItemPosition);
        // 先判断类型是否一致,类型不同肯定不是同一个Item
        if (oldItem.getItemViewType() != newItem.getItemViewType()) {
            return false;
        }
        // 类型一致的话,比较唯一ID
        return oldItem.getUniqueId().equals(newItem.getUniqueId());
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        BaseRecyclerItem oldItem = oldList.get(oldItemPosition);
        BaseRecyclerItem newItem = newList.get(newItemPosition);
        // 直接调用重写的equals方法比较所有内容
        return oldItem.equals(newItem);
    }

    // 可选:局部更新优化(只刷新变化的字段,提升性能)
    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        BaseRecyclerItem oldItem = oldList.get(oldItemPosition);
        BaseRecyclerItem newItem = newList.get(newItemPosition);
        Bundle payload = new Bundle();

        if (oldItem instanceof ViewType1 && newItem instanceof ViewType1) {
            ViewType1 oldV1 = (ViewType1) oldItem;
            ViewType1 newV1 = (ViewType1) newItem;
            if (!Objects.equals(oldV1.getTitle(), newV1.getTitle())) {
                payload.putString("title", newV1.getTitle());
            }
            if (!Objects.equals(oldV1.getContent(), newV1.getContent())) {
                payload.putString("content", newV1.getContent());
            }
        } else if (oldItem instanceof ViewType2 && newItem instanceof ViewType2) {
            ViewType2 oldV2 = (ViewType2) oldItem;
            ViewType2 newV2 = (ViewType2) newItem;
            if (!Objects.equals(oldV2.getImageUrl(), newV2.getImageUrl())) {
                payload.putString("imageUrl", newV2.getImageUrl());
            }
            if (oldV2.getCount() != newV2.getCount()) {
                payload.putInt("count", newV2.getCount());
            }
        }

        // 没有变化的字段就返回null,走全量刷新逻辑
        return payload.isEmpty() ? null : payload;
    }
}

第三步:在Adapter中使用DiffUtil更新数据

当你从API拿到新数据后,这样高效更新RecyclerView:

// 假设你的Adapter里有数据源:private List<BaseRecyclerItem> mItems;
public void updateData(List<BaseRecyclerItem> newItems) {
    DiffUtil.Callback callback = new TestDiffCallback(mItems, newItems);
    DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback);
    // 替换旧数据
    mItems.clear();
    mItems.addAll(newItems);
    // 通知Adapter执行差异化更新
    result.dispatchUpdatesTo(this);
}

关键注意点

  • 唯一ID要靠谱:getUniqueId()必须返回服务器生成的唯一标识,绝对不能用Item的position(position会随列表变化而改变)。
  • 局部更新的妙用:如果你的Item布局复杂,重写getChangePayload可以避免整个Item刷新,大幅提升流畅度。在Adapter的onBindViewHolder里可以这样处理payload:
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            // 全量刷新,绑定所有数据
            onBindViewHolder(holder, position);
        } else {
            // 局部更新,只处理变化的字段
            Bundle payload = (Bundle) payloads.get(0);
            if (holder instanceof ViewType1ViewHolder) {
                ViewType1ViewHolder vh = (ViewType1ViewHolder) holder;
                if (payload.containsKey("title")) {
                    vh.titleTv.setText(payload.getString("title"));
                }
                if (payload.containsKey("content")) {
                    vh.contentTv.setText(payload.getString("content"));
                }
            } else if (holder instanceof ViewType2ViewHolder) {
                ViewType2ViewHolder vh = (ViewType2ViewHolder) holder;
                if (payload.containsKey("imageUrl")) {
                    // 加载新图片
                    Glide.with(vh.itemView).load(payload.getString("imageUrl")).into(vh.imageIv);
                }
                if (payload.containsKey("count")) {
                    vh.countTv.setText(String.valueOf(payload.getInt("count")));
                }
            }
        }
    }
    

这样你的多类型RecyclerView就能高效完成差异化更新啦!

内容的提问来源于stack exchange,提问作者spoonsearch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:53:14