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

Android Navigation+ViewModel:返回Fragment时保留RecyclerView滚动位置

解决RecyclerView返回后滚动状态丢失的问题

问题根源

从GameDetailFragment返回时,GamesGridFragment的视图会重建(onCreateView再次执行),尽管ViewModel保留了数据,但新创建的RecyclerView没有留存之前的滚动位置;再加上LiveData的观察者会再次收到缓存数据,触发refreshAdapter执行全量刷新,直接导致滚动状态重置。


1. 保存并恢复RecyclerView滚动状态

在Fragment中通过生命周期方法保存LayoutManager的状态,等数据加载完成后再恢复:

// 成员变量存储滚动状态
private Parcelable recyclerViewState;

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    // 保存当前RecyclerView的滚动位置
    recyclerViewState = binding.recyclerView.getLayoutManager().onSaveInstanceState();
    outState.putParcelable("RECYCLER_STATE", recyclerViewState);
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // 初始化Adapter和RecyclerView
    gamesAdapter = new GamesAdapter();
    binding.recyclerView.setAdapter(gamesAdapter);
    
    // 从 savedInstanceState 中恢复之前保存的状态
    if (savedInstanceState != null) {
        recyclerViewState = savedInstanceState.getParcelable("RECYCLER_STATE");
    }

    gamesViewModel.getGameItems().observe(getViewLifecycleOwner(), gameItems -> {
        gamesAdapter.refreshAdapter(gameItems);
        binding.progressBar.setVisibility(View.GONE);
        // 数据更新完成后恢复滚动状态
        if (recyclerViewState != null) {
            binding.recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
            recyclerViewState = null; // 恢复后清空,防止重复执行
        }
    });
}

2. 优化Adapter刷新逻辑,避免无意义全量刷新

当前refreshAdapter的逻辑不管数据是否变化都会强制全量刷新,这是导致滚动状态丢失的关键诱因之一,优化方式有两种:

方式一:判断数据一致时跳过刷新

如果GameItem正确实现了equals方法,可以直接对比新旧数据:

public void refreshAdapter(List<GameItem> elements) {
    // 数据无变化则直接返回
    if (displayedElements.equals(elements)) {
        return;
    }
    notifyItemRangeRemoved(0, displayedElements.size());
    displayedElements.clear();
    displayedElements.addAll(elements);
    notifyItemRangeChanged(0, displayedElements.size());
}

方式二:用DiffUtil实现高效增量更新

这种方式只会更新真正变化的条目,性能更优,也能避免不必要的视图重置:
首先创建Diff回调类:

public class GameItemDiffCallback extends DiffUtil.Callback {
    private final List<GameItem> oldList;
    private final List<GameItem> newList;

    public GameItemDiffCallback(List<GameItem> oldList, List<GameItem> 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 oldPos, int newPos) {
        // 用GameItem的唯一标识判断是否为同一条目(比如id)
        return oldList.get(oldPos).getId() == newList.get(newPos).getId();
    }

    @Override
    public boolean areContentsTheSame(int oldPos, int newPos) {
        // 判断条目内容是否一致,依赖GameItem正确实现equals方法
        return oldList.get(oldPos).equals(newList.get(newPos));
    }
}

然后修改refreshAdapter:

public void refreshAdapter(List<GameItem> elements) {
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new GameItemDiffCallback(displayedElements, elements));
    displayedElements.clear();
    displayedElements.addAll(elements);
    diffResult.dispatchUpdatesTo(this);
}

3. 规范视图初始化逻辑

把RecyclerView和Adapter的初始化代码移到onViewCreated中,而不是onCreateView,符合Fragment生命周期的最佳实践,也能避免视图重建时的重复初始化问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 18:23:19