RecyclerView Adapter的onBindViewHolder多次调用?DiffUtil+Firestore问题排查
这种情况绝对不正常,你的代码确实存在几个关键问题,导致DiffUtil完全没发挥它的作用,咱们一步步拆解问题和修复方案:
核心问题分析
每次快照回调都新建Adapter实例
你在onEvent方法里每次收到Firestore快照时,都重新创建了CommentsListAdapter,还重新给RecyclerView设置了这个新Adapter。这相当于直接替换了整个RecyclerView的适配器,DiffUtil根本没机会去对比新旧列表的差异——因为适配器都是全新的,RecyclerView只能被迫重新绑定所有Item。DiffUtil的内容对比逻辑不准确
你当前的areContentsTheSame只对比了commentId,这会导致如果同一个评论的内容或时间发生更新时,DiffUtil会错误地认为内容没有变化,不会触发Item的更新。这部分逻辑需要覆盖所有你在onBindViewHolder里用到的显示字段。
修复步骤
1. 把Adapter实例提升为成员变量,避免重复创建
在你的Activity/Fragment里把Adapter定义为成员变量,只初始化一次:
// 作为Activity/Fragment的成员变量 private CommentsListAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ...其他初始化逻辑(比如设置布局、找到RecyclerView) adapter = new CommentsListAdapter(context); commentsRecyclerView.setAdapter(adapter); }
2. 修改快照回调逻辑,只更新数据
去掉onEvent里新建Adapter和重新设置Adapter的代码,只负责生成新列表并提交给已有的Adapter:
@Override protected void onStart() { super.onStart(); reference.addSnapshotListener((Activity)context, new EventListener<QuerySnapshot>() { @Override public void onEvent(@Nullable QuerySnapshot queryDocumentSnapshots, @Nullable FirebaseFirestoreException e) { if (e != null) { Toast.makeText(context, "Error", Toast.LENGTH_SHORT).show(); return; } List<Comment> newComments = new ArrayList<>(); for (QueryDocumentSnapshot snapshot : queryDocumentSnapshots) { Comment comment = snapshot.toObject(Comment.class).withId(snapshot.getId()); newComments.add(comment); } // 提交新列表给Adapter,DiffUtil会自动计算差异 adapter.submitList(newComments); // 注意这里要减1,因为RecyclerView的position从0开始 commentsRecyclerView.smoothScrollToPosition(adapter.getItemCount() - 1); } }); }
3. 修正DiffUtil的内容对比逻辑
确保areContentsTheSame对比所有需要显示的字段,而不仅仅是ID:
private static final DiffUtil.ItemCallback<Comment> DIFF_CALLBACK = new DiffUtil.ItemCallback<Comment>() { @Override public boolean areItemsTheSame(@NonNull Comment oldItem, @NonNull Comment newItem) { // 用唯一ID判断是否为同一个Item,这部分是对的 return oldItem.commentId.equals(newItem.commentId); } @Override public boolean areContentsTheSame(@NonNull Comment oldItem, @NonNull Comment newItem) { // 对比所有在onBindViewHolder中用到的显示字段 return oldItem.commentId.equals(newItem.commentId) && oldItem.getComment().equals(newItem.getComment()) && oldItem.getCommentDateCreated().equals(newItem.getCommentDateCreated()); } };
修复后的效果
当你复用同一个Adapter并调用submitList时,ListAdapter内部会自动用DiffUtil计算新旧列表的差异,只会对新增的Item(或者内容有变化的Item)调用onBindViewHolder,其余已有的Item不会被重新绑定,完美实现你想要的局部更新效果。
内容的提问来源于stack exchange,提问作者eegooDeveloper

