ViewModel更新时Fragment内RecyclerView自动复位至初始位置(未重建RecyclerView或调用notifyDataSetChanged)
我仔细看了你的代码,发现问题主要出在ViewPager2的Fragment适配器逻辑错误,以及Tab文本更新触发的ViewPager2内部状态变化,具体来说:
1. 适配器创建Fragment的逻辑完全写反了
看你PlannerFragmentViewPager2Adaptor里的createFragment方法,比如第0个位置的代码:
if(localeChoosingFragment !=null) localeChoosingFragment = new LocaleChoosingFragment(mContext, japanGuideViewModel); return localeChoosingFragment;
这逻辑是如果Fragment已经存在,反而重新新建一个!正常逻辑应该是如果Fragment为null才新建,否则复用已有的实例。
当你调用tab.setText()更新第二个Tab的文本时,TabLayoutMediator会触发ViewPager2的内部更新,导致适配器重新调用createFragment方法。此时因为你的逻辑错误,会新建一个LocaleChoosingFragment实例——虽然你觉得Fragment没重建,但实际上已经被替换成新的了,新的Fragment里的RecyclerView自然会回到初始位置。
修正这个逻辑
把适配器里的判断反过来,改为复用已有的Fragment实例:
@Override public Fragment createFragment(int position) { Log.d(TAG, "createFragment called in plannerFragment. Position is " + position); switch (position) { case 0: Log.d(TAG, "planner fragment, case 0, localechoosingfragment"); // 如果实例为空才新建,否则复用 if(localeChoosingFragment == null) { localeChoosingFragment = new LocaleChoosingFragment(mContext, japanGuideViewModel); } return localeChoosingFragment; case 1: Log.d("planner fragment localeExploringFragmentTAG", "case 1"); if(localeExploringFragment == null) { localeExploringFragment = new LocaleExploringFragment(mContext, japanGuideViewModel); } return localeExploringFragment; default: Log.d(TAG, "default constructor so returning localeChoosingFragment"); if(localeChoosingFragment == null) { localeChoosingFragment = new LocaleChoosingFragment(mContext, japanGuideViewModel); } return localeChoosingFragment; } }
2. 额外建议:保存RecyclerView的滚动位置
即使修复了适配器逻辑,有时候ViewPager2的Fragment切换、页面回收也可能导致滚动位置丢失。你可以在LocaleChoosingFragment里手动保存和恢复滚动位置:
在LocaleChoosingFragment里添加变量保存位置:
private int savedScrollPosition = 0;
然后在Fragment的生命周期方法里保存位置,比如onPause:
@Override public void onPause() { super.onPause(); // 保存当前可见的第一个item位置 savedScrollPosition = recyclerViewLayoutManager.findFirstVisibleItemPosition(); }
在onViewCreated里恢复位置:
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); Log.d(TAG, "onviewcreated in locale choosing fragment."); localeChoosingAdaptor = new LocaleChoosingRecyclerViewAdaptor(mContext, japanGuideViewModel); SnapHelper = new SnapHelperOneByOne(); recyclerViewLayoutManager = new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false); localeChoosingRecyclerView.setLayoutManager(recyclerViewLayoutManager); localeChoosingRecyclerView.setAdapter(localeChoosingAdaptor); SnapHelper.attachToRecyclerView(localeChoosingRecyclerView); // 恢复之前的滚动位置 if(savedScrollPosition != 0) { localeChoosingRecyclerView.scrollToPosition(savedScrollPosition); } }
这样即使Fragment被临时回收再重建,也能回到之前的滚动位置。
3. 补充说明
你提到Fragment没有被重建,但实际上因为适配器的错误逻辑,每次触发createFragment都会新建实例,所以Fragment是被重建了的——只是你可能没注意到日志里的constructor called for localeChoosingFragment会重复打印。修复适配器逻辑后,这个问题应该就能解决。
备注:内容来源于stack exchange,提问作者Jesse Duffield

