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

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,不会触发全列表重绘,不需要手动保存状态就能自动保留滚动位置,性能也更好。

  1. 先实现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());
    }
}
  1. 更新数据时用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 13:09:11