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
相关产品推荐
相关产品推荐

