RecyclerView刷新数据时如何保持滚动位置不返回顶部
RecyclerView刷新数据后保留滚动位置实现方案
你遇到的滚动位置回到顶部的问题,和clear()方法本身无关,核心原因是两点:一是你使用了会触发列表全量重绘的刷新方式,RecyclerView会丢弃所有已保存的布局状态、从顶部开始重新绘制;二是数据更新逻辑和Adapter的刷新方法搭配不当。
问题排查
你当前代码的几个明显问题:
- 每次调用
getData()都新建Volley请求队列,会造成不必要的内存占用,应该全局复用同一个RequestQueue实例。 - 你在
adapterPush()方法里大概率调用了adapter.notifyDataSetChanged(),这个方法会标记整个数据集为无效,强制RecyclerView重绘所有item,完全放弃之前保存的滚动位置、ViewHolder缓存,自然会回到列表顶部。 - 移除
category.clear()后数据重复是必然结果:每次请求返回的数据都会被追加到现有列表尾部,没有替换旧数据,和滚动位置异常是两个独立的问题。
可落地的解决方法
方案1:手动保存/恢复布局状态(适配全量刷新场景)
如果不需要做精细的差异更新,全量替换数据时可以在刷新前提前存好LayoutManager的状态,数据更新完成后恢复即可,即使调用notifyDataSetChanged()也不会跳回顶部。
修改你的刷新逻辑:
@Override public void onRefresh() { // 不要在这里直接clear列表,等新数据拉取成功、准备更新UI前再操作 JsonArrayRequest arrayRequest = new JsonArrayRequest(url_get, response -> { // 1. 先解析新数据到临时列表,不要直接操作全局category List<Category> newList = new ArrayList<>(); JSONObject jsonObject; for (int i = 0; i < response.length(); i++) { try { jsonObject = response.getJSONObject(i); Category cat = new Category(); cat.setId(jsonObject.getString("id")); cat.setCategory(jsonObject.getString("name")); cat.setPvisited(jsonObject.getString("person_visited")); cat.setCompany(jsonObject.getString("company")); cat.setDate(jsonObject.getString("date")); newList.add(cat); } catch (JSONException e) { e.printStackTrace(); } } // 2. 保存当前滚动状态 RecyclerView.LayoutManager lm = recyclerView.getLayoutManager(); Parcelable scrollState = null; if (lm != null) { scrollState = lm.onSaveInstanceState(); } // 3. 更新全局数据 category.clear(); category.addAll(newList); adapter.notifyDataSetChanged(); // 4. 恢复滚动位置 if (lm != null && scrollState != null) { lm.onRestoreInstanceState(scrollState); } refresh.setRefreshing(false); }, error -> { refresh.setRefreshing(false); }); // 替换成全局单例的RequestQueue,不要每次new requestQueue.add(arrayRequest); }
方案2:使用DiffUtil做差异刷新(更优方案)
用DiffUtil自动计算新旧数据集的差异,只更新变化的item,不会触发全列表重绘,不需要手动保存状态就能自动保留滚动位置,性能也更好。
- 先实现DiffUtil回调:
public class CategoryDiffCallback extends DiffUtil.Callback { private final List<Category> oldList; private final List<Category> newList; public CategoryDiffCallback(List<Category> oldList, List<Category> 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判断是否为同一条目 return oldList.get(oldItemPosition).getId().equals(newList.get(newItemPosition).getId()); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { // 判断条目内容是否完全一致 Category oldCat = oldList.get(oldItemPosition); Category newCat = newList.get(newItemPosition); return oldCat.getCategory().equals(newCat.getCategory()) && oldCat.getPvisited().equals(newCat.getPvisited()) && oldCat.getCompany().equals(newCat.getCompany()) && oldCat.getDate().equals(newCat.getDate()); } }
- 更新数据时用DiffUtil分发更新事件:
// 解析完新数据newList后 DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new CategoryDiffCallback(category, newList)); category.clear(); category.addAll(newList); diffResult.dispatchUpdatesTo(adapter); refresh.setRefreshing(false);
额外优化建议
- 不要在外部直接修改Adapter持有的数据引用,最好在Adapter内部封装
updateData()方法,把数据更新、刷新逻辑收口,避免外部随意修改列表导致Adapter状态和数据不一致。 - 不要每次请求都新建Volley RequestQueue,在Application类里初始化一次全局复用即可。
内容的提问来源于stack exchange,提问作者Ezoxe
相关产品推荐
相关产品推荐

