ViewPager2返回后点击Tab不加载Fragment但滑动正常问题
问题现象
- 基于ViewPager2 + TabLayout实现多页签切换,初始状态下滑动切换、点击Tab均可正常加载对应Fragment
- 现有Tab A、Tab B、Tab C、Tab D等多个页签,从Tab B跳转打开其子Fragment Tab B1,按返回键回到Tab B后,点击Tab A会出现Tab A高亮选中,但无法加载对应Fragment的异常,滑动切换功能仍可正常运行
原有实现代码:
private val fragmentList = arrayListOf<BaseMvvmFragment<*, *>>( TabAFragment(), TabBFragment(), TabCFragment(), TabDFragment(), ) private fun setupPager() { val adapter = ViewPagerAdapter( fragmentList, childFragmentManager, lifecycle ) binding.mainViewPager.adapter = adapter TabLayoutMediator(binding.pageIndicator, binding.mainViewPager) { tab, position -> fragmentList[position].titleRes?.let { tab.text = getString(it) } }.attach() binding.mainViewPager.setCurrentItem( fragmentList.indexOfFirst { it is TabAFragment }, false ) }
问题根因
异常由两个不规范实现共同触发:
- 预创建Fragment列表传入Adapter的写法不符合
FragmentStateAdapter设计规范。FragmentStateAdapter本身会通过FragmentManager统一管理Fragment的创建、缓存、状态恢复,你外部维护的fragmentList里的实例,在从B1返回触发Fragment生命周期重建后,会和FragmentManager实际缓存的Fragment实例脱节,如果你的自定义Adapter中createFragment逻辑是直接从fragmentList按位置取实例,就会出现位置映射错乱。 - 子Fragment进出返回栈的操作,会临时打断
TabLayoutMediator的Tab点击事件监听绑定。滑动切换是ViewPager2主动触发页面变更,变更完成后反向同步Tab选中状态,所以不受绑定失效影响;点击Tab是Tab主动触发ViewPager2切页,绑定失效后就会出现Tab高亮、页面不切换的问题。
修复方案
- 废弃外部预创建的fragmentList,修改Adapter实现,由Adapter自己负责按位置创建Fragment,不要手动持有Fragment实例。正确的Adapter写法参考:
// 如果ViewPager2在Activity中,用这个构造 class ViewPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) { // 如果ViewPager2嵌套在Fragment中,替换构造为: // class ViewPagerAdapter(hostFragment: Fragment) : FragmentStateAdapter(hostFragment) override fun getItemCount(): Int = 4 override fun createFragment(position: Int): Fragment { return when(position) { 0 -> TabAFragment() 1 -> TabBFragment() 2 -> TabCFragment() 3 -> TabDFragment() else -> throw IllegalArgumentException("非法位置:$position") } } } - 调整setupPager逻辑,移除对外部fragmentList的依赖:
private fun setupPager() { // ViewPager在Activity中传this,嵌套在Fragment中传this@宿主Fragment类名 val adapter = ViewPagerAdapter(this) binding.mainViewPager.adapter = adapter TabLayoutMediator(binding.pageIndicator, binding.mainViewPager) { tab, position -> val titleRes = when(position) { 0 -> R.string.tab_a_title 1 -> R.string.tab_b_title 2 -> R.string.tab_c_title 3 -> R.string.tab_d_title else -> null } titleRes?.let { tab.text = getString(it) } }.attach() // 初始选中Tab A,对应位置0 binding.mainViewPager.setCurrentItem(0, false) } - 跳转Tab B1时,优先使用Tab B自身的
childFragmentManager执行Fragment事务,不要操作ViewPager所属宿主的FragmentManager栈,避免打乱ViewPager的Fragment状态。如果必须使用外层FragmentManager,可以在Tab B的onResume生命周期中加一行状态同步代码,兜底修正选中状态:override fun onResume() { super.onResume() // 同步Tab选中状态和ViewPager当前页 binding.pageIndicator.getTabAt(binding.mainViewPager.currentItem)?.select() } - 检查是否给TabLayout、ViewPager2手动添加过
OnTabSelectedListener或OnPageChangeCallback,确认没有在回调中添加拦截逻辑阻断点击事件传递,跳转子Fragment时如果注销过监听,返回时要重新注册。
内容的提问来源于stack exchange,提问作者DroidBee
相关产品推荐
相关产品推荐

