Android中BottomNavView与Navigation组件的Fragment状态保存方法
问题描述
我的应用使用Navigation component搭配BottomNavigationView展示多个Fragment。编写了如下代码,但切换BottomNavigationView的Fragment时,Fragment会被重建!我想要保存Fragment状态,避免Fragment被重建!
XML代码
<!--Fragments--> <androidx.fragment.app.FragmentContainerView android:id="@+id/navHost" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_main" /> <!--Bottom nav--> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNav" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:menu="@menu/menu_home" />
Activity代码
private lateinit var navHost: NavHostFragment override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) _binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) navHost = supportFragmentManager.findFragmentById(R.id.navHost) as NavHostFragment binding.bottomNav.apply { setupWithNavController(navHost.navController) //Disable double click on item setOnNavigationItemReselectedListener {} } } override fun onNavigateUp(): Boolean { return navHost.navController.navigateUp() || super.onNavigateUp() }
点击BottomNavigationView项时,如何保存Fragment的状态并避免其被重建?
解决方案
Navigation Component默认的setupWithNavController每次切换都会重新创建Fragment,要实现状态保留,可通过以下两种方式解决:
方式一:手动用FragmentManager管理Fragment实例
放弃Navigation Component的自动切换逻辑,直接通过FragmentManager添加、隐藏、显示Fragment,确保实例被保留,状态不丢失。
步骤1:修改布局容器
将原NavHostFragment替换为普通FrameLayout作为Fragment容器:
<!--Fragment容器--> <FrameLayout android:id="@+id/fragmentContainer" android:layout_width="match_parent" android:layout_height="match_parent" /> <!--Bottom nav--> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNav" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:menu="@menu/menu_home" />
步骤2:实现切换逻辑
在Activity中维护Fragment实例,切换时仅做隐藏/显示操作:
private lateinit var binding: ActivityMainBinding // 缓存已创建的Fragment实例 private val fragmentMap = mutableMapOf<Int, Fragment>() private var currentFragment: Fragment? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // 初始化所有需要切换的Fragment并存入缓存 fragmentMap[R.id.nav_home] = HomeFragment() fragmentMap[R.id.nav_dashboard] = DashboardFragment() fragmentMap[R.id.nav_notifications] = NotificationsFragment() // 默认显示第一个Fragment switchFragment(R.id.nav_home) // 底部导航点击事件 binding.bottomNav.setOnItemSelectedListener { menuItem -> switchFragment(menuItem.itemId) true } // 禁用重复点击 binding.bottomNav.setOnNavigationItemReselectedListener {} } private fun switchFragment(itemId: Int) { val targetFragment = fragmentMap[itemId] ?: return val transaction = supportFragmentManager.beginTransaction() currentFragment?.let { transaction.hide(it) } if (!targetFragment.isAdded) { transaction.add(R.id.fragmentContainer, targetFragment) } else { transaction.show(targetFragment) } transaction.commit() currentFragment = targetFragment }
这种方式完全控制Fragment生命周期,切换无重建,状态自然保留。
方式二:基于Navigation Component的状态保存/恢复
若不想放弃Navigation Component,可通过拦截导航事件手动保存、恢复Fragment状态,需处理更多边界情况:
private lateinit var navHost: NavHostFragment // 缓存每个目的地的Fragment状态 private val fragmentStates = mutableMapOf<Int, Bundle?>() private var currentDestinationId: Int = -1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) _binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) navHost = supportFragmentManager.findFragmentById(R.id.navHost) as NavHostFragment // 监听导航切换事件,保存前一个Fragment状态,恢复目标Fragment状态 navHost.navController.addOnDestinationChangedListener { _, destination, _ -> if (currentDestinationId != -1 && currentDestinationId != destination.id) { val currentFragment = navHost.childFragmentManager.primaryNavigationFragment fragmentStates[currentDestinationId] = currentFragment?.saveState() } currentDestinationId = destination.id val targetFragment = navHost.childFragmentManager.primaryNavigationFragment fragmentStates[destination.id]?.let { targetFragment?.restoreState(it) } } binding.bottomNav.apply { setupWithNavController(navHost.navController) setOnNavigationItemReselectedListener {} } } // 保存状态到Activity的savedInstanceState override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putSerializable("fragmentStates", fragmentStates as Serializable) outState.putInt("currentDestinationId", currentDestinationId) } // 恢复Activity状态时重新加载Fragment状态 override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) @Suppress("UNCHECKED_CAST") fragmentStates.putAll(savedInstanceState.getSerializable("fragmentStates") as Map<Int, Bundle?>) currentDestinationId = savedInstanceState.getInt("currentDestinationId") }
注意:此方式依赖Fragment的saveState()/restoreState(),需确保Fragment内视图状态通过onSaveInstanceState或ViewModel正确存储。
额外建议
- 优先使用
ViewModel存储业务数据,其生命周期独立于Fragment视图,即使Fragment意外重建,数据也不会丢失。 - 若使用方式一,需注意内存占用,Activity销毁时要确保Fragment实例被正确清理,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Dali Hasan
相关产品推荐
相关产品推荐

