使用NavGraph结合Bottom Navigation时阻止Fragment重复刷新的方案
问题
我目前使用NavigationGraph配合底部导航栏,每次点击底部导航按钮时Fragment都会被重建。我希望像Twitter、Instagram这类应用一样,当Fragment已经创建过之后,切换底部导航时不再刷新该Fragment。现在返回MainFragment时,所有视图都会重新创建,还会触发不必要的数据库拉取操作。我查过相关问题但没找到合适的方案,求建议。
截图说明:从其他Fragment返回MainFragment时,MainFragment会被重新创建。
相关代码
MainActivity 代码
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) navController = findNavController(R.id.main_fragmentContainerView) val appBarConfiguration = AppBarConfiguration( setOf( R.id.mainFragment, R.id.searchFragment, R.id.userProfileFragment ) ) bottomNavigationView.setupWithNavController(navController) setupActionBarWithNavController(navController, appBarConfiguration) } override fun onNavigateUp(): Boolean { return navController.navigateUp() || super.onSupportNavigateUp() }
MainFragment 代码
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) readDatabase() }
解决方案
Navigation组件默认的setupWithNavController每次切换都会重建Fragment,要实现保留实例的效果,推荐以下几种方案:
方案1:自定义底部导航逻辑(最稳妥)
放弃Navigation组件自带的绑定逻辑,自己管理Fragment的切换与实例缓存,完全控制生命周期:
- 把布局里的
NavHostFragment替换成普通的FragmentContainerView(或FrameLayout),id设为fragment_container。 - 在MainActivity中维护Fragment实例缓存,切换时只做show/hide操作:
class MainActivity : AppCompatActivity() { private val fragmentCache = mutableMapOf<Int, Fragment>() private lateinit var bottomNav: BottomNavigationView private val containerId = R.id.fragment_container override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bottomNav = findViewById(R.id.bottomNavigationView) // 初始化默认显示的Fragment if (savedInstanceState == null) { val mainFragment = MainFragment() fragmentCache[R.id.mainFragment] = mainFragment supportFragmentManager.beginTransaction() .add(containerId, mainFragment, "main") .commit() bottomNav.selectedItemId = R.id.mainFragment } // 设置底部导航点击事件 bottomNav.setOnItemSelectedListener { menuItem -> val targetId = menuItem.itemId // 从缓存取Fragment,不存在则创建 val targetFragment = fragmentCache.getOrPut(targetId) { when(targetId) { R.id.mainFragment -> MainFragment() R.id.searchFragment -> SearchFragment() R.id.userProfileFragment -> UserProfileFragment() else -> MainFragment() } } // 切换Fragment:隐藏其他,显示目标 supportFragmentManager.beginTransaction().apply { fragmentCache.values.forEach { hide(it) } if (!targetFragment.isAdded) { add(containerId, targetFragment, targetId.toString()) } show(targetFragment) setPrimaryNavigationFragment(targetFragment) commit() } true } } }
这种方式切换时Fragment不会重建,只会触发onHiddenChanged,完美保留之前的视图状态和数据,没有额外的数据库请求。
方案2:修改Navigation默认行为(有兼容性风险)
如果一定要保留NavigationGraph的使用,可以自定义FragmentNavigator来复用已有的Fragment实例,但要注意和其他Navigation功能的兼容性:
- 创建自定义Navigator:
class KeepStateFragmentNavigator( context: Context, manager: FragmentManager, containerId: Int ) : FragmentNavigator(context, manager, containerId) { override fun navigate( destination: Destination, args: Bundle?, navOptions: NavOptions?, navigatorExtras: Navigator.Extras? ): NavDestination? { val tag = destination.id.toString() val transaction = manager.beginTransaction() var fragment = manager.findFragmentByTag(tag) if (fragment == null) { fragment = instantiateFragment(context, manager, destination.className, args) transaction.add(containerId, fragment, tag) } // 隐藏其他已加载的Fragment manager.fragments.forEach { if (it.tag != tag) transaction.hide(it) } transaction.show(fragment) transaction.setPrimaryNavigationFragment(fragment) transaction.commit() return destination } }
- 在MainActivity中替换默认的Navigator:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_fragmentContainerView) as NavHostFragment val navController = navHostFragment.navController // 替换默认的FragmentNavigator val customNavigator = KeepStateFragmentNavigator(this, supportFragmentManager, R.id.main_fragmentContainerView) navController.navigatorProvider.addNavigator(customNavigator) // 重新加载导航图 navController.setGraph(R.navigation.nav_graph) val appBarConfiguration = AppBarConfiguration( setOf( R.id.mainFragment, R.id.searchFragment, R.id.userProfileFragment ) ) bottomNavigationView.setupWithNavController(navController) setupActionBarWithNavController(navController, appBarConfiguration) }
这种方式能保留Navigation的大部分特性,但可能和返回栈、深层链接等功能冲突,需要额外测试。
临时优化:避免重复请求数据
如果暂时不想改动导航逻辑,先修改MainFragment,只在首次创建时拉取数据:
class MainFragment : Fragment() { private var isDataLoaded = false override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 仅首次创建时加载数据 if (!isDataLoaded) { readDatabase() isDataLoaded = true } } // 若需要在Fragment显示时刷新数据,可重写此方法 override fun onHiddenChanged(hidden: Boolean) { super.onHiddenChanged(hidden) if (!hidden) { // 可选:当Fragment重新显示时刷新数据 // readDatabase() } } }
这种方法只是避免了重复的数据库请求,但Fragment视图仍会重建,属于临时解决方案。
内容的提问来源于stack exchange,提问作者freud
相关产品推荐
相关产品推荐

