Navigation Component搭配BottomNavigationView切换Fragment频繁重建如何解决
问题根因
- 默认
Jetpack Navigation Component搭配BottomNavigationView的单NavHost实现,切换tab时采用replace事务加载Fragment,会触发目标Fragment的销毁重建,这是框架默认行为。你提到的官方多返回栈示例是多NavHost实现,你当前代码仅使用了单个全局NavHost,并没有启用多返回栈的Fragment保留逻辑。 - 你的
SharedViewModel作用域配置错误:如果在Fragment中通过by viewModels()获取ViewModel,其生命周期和当前Fragment绑定,Fragment销毁时ViewModel也会被回收,自然无法保留共享数据。
解决方案
方案1:快速修复数据丢失问题(无需改动页面切换逻辑)
修改SharedViewModel的作用域,让它的生命周期长于单个Fragment即可:
- 如果需要全局共享该
meetingID,直接在两个Fragment中用by activityViewModels()获取ViewModel,这样ViewModel和Activity生命周期绑定,切换Fragment时不会被销毁:
// 在HomeFragment和MeetingFragment中替换原有的ViewModel获取代码 val sharedViewModel: SharedViewModel by activityViewModels()
- 如果仅需要在两个首页Fragment间共享,不想污染Activity全局作用域,可以给首页相关页面套一个嵌套导航图:
- 在导航图文件中新增嵌套导航节点,id设为
@+id/main_tab_nav,把homeFragment和meetingFragment放到该嵌套节点下 - 在两个Fragment中通过导航图作用域获取ViewModel:
val sharedViewModel: SharedViewModel by navGraphViewModels(R.id.main_tab_nav) - 在导航图文件中新增嵌套导航节点,id设为
方案2:彻底解决Fragment切换重建问题
你可以启用官方多返回栈实现,切换tab时采用hide/show事务保留Fragment实例:
- 先将官方多返回栈示例中的
NavigationExtensions.kt工具类复制到你的项目中 - 拆分导航结构:将登录注册逻辑放到根导航图,Home和Meeting两个tab分别对应独立的子导航图
home_nav.xml和meeting_nav.xml
注意:需要将布局中原有的
NavHostFragment替换为id为nav_host_container的FrameLayout,作为多NavHost的容器。
- 替换
MainActivity的初始化逻辑:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) setSupportActionBar(binding.toolBar) appBarConfiguration = AppBarConfiguration( setOf(R.id.homeFragment, R.id.meetingFragment) ) // 每个tab对应子导航图id val navGraphIds = listOf(R.navigation.home_nav, R.navigation.meeting_nav) // 初始化多返回栈控制器 val navControllerLiveData = binding.bottomNavigation.setupWithNavController( navGraphIds = navGraphIds, fragmentManager = supportFragmentManager, containerId = R.id.nav_host_container, intent = intent ) // 绑定ActionBar和切换监听 navControllerLiveData.observe(this) { navController -> setupActionBarWithNavController(navController, appBarConfiguration) navController.addOnDestinationChangedListener { _, destination, _ -> binding.bottomNavigation.visibility = if ( destination.id == R.id.loginFragment || destination.id == R.id.registerFragment ) View.GONE else View.VISIBLE } } } override fun onSupportNavigateUp(): Boolean { val currentNavController = binding.bottomNavigation.currentNavController ?: return false return currentNavController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() }
修改后切换tab时不会销毁Fragment实例,原有页面状态会完全保留。
内容的提问来源于stack exchange,提问作者cjames
相关产品推荐
相关产品推荐

