代码设置NavHostFragment导航图时,如何保留其状态?
嘿,这个问题我之前也踩过坑!核心问题就是你每次在onViewCreated里重新设置导航图,把内部NavHost的回退栈和当前状态全清掉了——导致从Frag3返回Frag2时,内部导航直接回到初始的InnerFrag1,而且按返回键因为内部没回退栈了,就触发了外部导航的返回逻辑,直接跳到Frag1。
问题根源拆解
当你从Frag3返回Frag2时,Frag2的onViewCreated会再次执行,而你每次都在这里重新inflate导航图并设置给navController.graph——这个操作相当于完全重置了内部导航的状态,之前的InnerFrag2和回退栈记录都会被清空,所以NavHost只能回到默认的起始页面InnerFrag1,而且内部回退栈为空,你的BackPressedCallback自然也起不到作用,返回键直接走外部导航的逻辑。
解决方案
1. 仅在首次创建时设置导航图
利用savedInstanceState判断Frag2是否是首次创建:只有当savedInstanceState == null时,才加载并设置动态导航图。当从Frag3返回时,savedInstanceState不为null,说明是View重建,这时候NavHost会自动恢复之前的导航状态(包括回退栈),不需要重新设置导航图。
修改后的onViewCreated代码:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) requireActivity().addOnBackPressedCallback(backPressedCallback) // 仅首次创建Frag2时设置动态导航图 if (savedInstanceState == null) { val navHostFragment = childFragmentManager.findFragmentById(R.id.innerNavHostFragment) as NavHostFragment val inflater = navHostFragment.navController.navInflater val graph = inflater.inflate(navigationId) // 这里是你根据参数动态获取的导航图ID navHostFragment.navController.graph = graph } }
2. 优化BackPressedCallback的逻辑
原来的回调没有判断内部导航是否有可返回的页面,可能导致内部回退栈为空时还拦截返回键。我们可以让回调的启用状态和内部回退栈的状态绑定:
private val backPressedCallback = object : OnBackPressedCallback(false) { override fun handleOnBackPressed() { navHostFragment.navController.navigateUp() } // 只有当内部导航存在回退栈时,才启用这个回调 override fun isEnabled(): Boolean { return navHostFragment.navController.previousBackStackEntry != null } }
同时,监听内部导航的页面变化,实时更新回调的启用状态:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) requireActivity().addOnBackPressedCallback(backPressedCallback) val navHostFragment = childFragmentManager.findFragmentById(R.id.innerNavHostFragment) as NavHostFragment // 监听内部导航变化,更新回调状态 navHostFragment.navController.addOnDestinationChangedListener { _, _, _ -> backPressedCallback.isEnabled = navHostFragment.navController.previousBackStackEntry != null } // 仅首次创建时设置导航图 if (savedInstanceState == null) { val inflater = navHostFragment.navController.navInflater val graph = inflater.inflate(navigationId) navHostFragment.navController.graph = graph } }
3. 特殊场景处理(可选)
如果你的需求是Frag2的参数变化时需要切换内部导航图(而不仅仅是首次创建),那不能直接重置graph,否则会丢失状态。这种情况建议先清除内部回退栈,再导航到新导航图的起始页:
// 参数变化时的处理逻辑 val navController = navHostFragment.navController // 清除所有内部回退栈 navController.popBackStack(navController.graph.startDestinationId, false) // 加载新的导航图并导航 val newGraph = navController.navInflater.inflate(newNavigationId) navController.graph = newGraph
总结
核心思路就是让NavHostFragment自己管理导航状态,不要在每次View重建时都重置导航图。通过savedInstanceState判断首次创建时机,就能保留内部导航的回退栈和当前页面状态,返回键的逻辑也能正常工作了。
内容的提问来源于stack exchange,提问作者sasha199568

