使用DiffUtil.ItemCallback时ListAdapter初始数据被覆盖问题求助
我在应用里有两个LiveData对象,分别用来获取0-10条和11-20条数据。加载到RecyclerView的时候,本来应该显示20条,但前10条被新的10条替换了。下面是我的实现代码:
初始化代码
recyclerView = findViewById(R.id.recycler_view); adapter = new ItemsAdapter(); recyclerView.setAdapter(adapter); viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
获取初始数据方法
private void getInitialItems() { ItemListLiveData liveData = viewModel.getItemsLiveData(); liveData.observe(this, itemtList -> adapter.submitList(itemtList)); }
获取后续数据方法
private void getNextlItems() { ItemListLiveData liveData = viewModel.getItemsLiveData(); liveData.observe(this, itemtList -> adapter.submitList(itemtList)); }
ViewModel类
public class ItemListViewModel extends ViewModel { private ItemListRepository repository = new ItemListRepository(); ItemListLiveData getItemsLiveData() { return repository.getItemListLiveData(); } }
仓库类仅从后端服务器获取数据。
Adapter类
public class ItemsAdapter extends ListAdapter<Item, ItemsAdapter.ItemViewHolder> { ItemsAdapter() { super(diffCallback); } @NonNull @Override public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { //Inflate the view } @Override public void onBindViewHolder(@NonNull final ItemViewHolder holder, int position) { //Bind the Item according to position } private static DiffUtil.ItemCallback<Item> diffCallback = new DiffUtil.ItemCallback<Item>() { @Override public boolean areItemsTheSame(@NonNull Item oldItem, @NonNull Item newItem) { return oldItem.id.equals(newItem.id); } @Override public boolean areContentsTheSame(@NonNull Item oldItem, @NonNull Item newItem) { return oldItem.equals(newItem); } }; }
我本来以为用了DiffUtil.ItemCallback之后,因为所有对象都是不同的,应该能得到累积的20条数据,但最后只显示了11-20这10条。请问怎么用submitList实现显示完整的20条数据?
其实问题的核心很简单:你每次调用submitList()都是直接把新的10条数据丢给适配器,而不是把旧数据和新数据合并后再提交。DiffUtil确实能帮RecyclerView高效更新,但它不会自动帮你合并数据——它只是对比你提交的新旧两个列表,然后做对应的增删改操作。
咱们一步步来改:
在ViewModel里维护一个累积的数据集
别再每次返回单独的新列表了,在ViewModel里存一个总的列表,拿到新数据就往里面加,再通过LiveData把完整列表发出去。比如修改你的ViewModel:public class ItemListViewModel extends ViewModel { private ItemListRepository repository = new ItemListRepository(); private MutableLiveData<List<Item>> combinedItemsLiveData = new MutableLiveData<>(); private List<Item> accumulatedItems = new ArrayList<>(); // 加载初始的0-10条数据 void loadInitialItems() { // 假设仓库里有对应的方法返回初始数据的LiveData repository.getInitialItemsLiveData().observeForever(newItems -> { accumulatedItems.clear(); // 先清空,防止重复加载 accumulatedItems.addAll(newItems); // 要传新的ArrayList实例,保证LiveData能检测到变化 combinedItemsLiveData.postValue(new ArrayList<>(accumulatedItems)); }); } // 加载后续的11-20条数据 void loadNextItems() { // 仓库里对应的方法返回下一页数据的LiveData repository.getNextItemsLiveData().observeForever(newItems -> { accumulatedItems.addAll(newItems); combinedItemsLiveData.postValue(new ArrayList<>(accumulatedItems)); }); } // 给外部提供观察完整列表的入口 LiveData<List<Item>> getCombinedItemsLiveData() { return combinedItemsLiveData; } }这里需要你调整仓库类,把原来的
getItemListLiveData()拆成getInitialItemsLiveData()和getNextItemsLiveData(),分别返回对应页的LiveData。修改Activity/Fragment里的观察逻辑
只需要观察一次合并后的LiveData就行,不用分别调用两次观察。初始化时加载第一页,需要加载下一页时调用对应的方法:// 初始化代码里添加 viewModel.getCombinedItemsLiveData().observe(this, itemList -> adapter.submitList(itemList)); viewModel.loadInitialItems(); // 比如用户滑动到底部时,调用加载下一页 // viewModel.loadNextItems();为啥之前的方式不行?
你之前两次调用getItemsLiveData(),每次都直接把新的10条列表submitList(),这相当于告诉RecyclerView:“现在整个列表就换成这10条了”,而不是“把这10条加到原来的后面”。DiffUtil对比旧列表(10条)和新列表(10条),发现所有item的id都不一样,就会把旧的10条全删掉,再加上新的10条,自然就只显示新数据了。
另外提醒个小细节:提交列表的时候一定要传新的ArrayList实例(比如new ArrayList<>(accumulatedItems)),别直接传原来的accumulatedItems。因为LiveData默认是对比引用是否变化的,如果列表内容变了但引用没变,它可能不会触发更新。
这样调整之后,每次加载新数据都会把新内容追加到总列表里,再提交给适配器,RecyclerView就会显示完整的20条数据啦。
内容的提问来源于stack exchange,提问作者Jorn

