Navigation Component搭配BottomNavigationView无法保存Fragment状态
Navigation 2.4.0+ 自带的BottomNavigationView多返回栈支持有多个强依赖的前置条件,任意一个不满足就会退回到旧的Fragment替换逻辑,导致切换tab时Fragment重建、状态丢失,90%以上的同类问题都是以下原因导致:
- 底部导航菜单的item id与导航图中对应Fragment目的地的id不匹配。这是最高发的问题,
setupWithNavController只有在两者id完全一致时才会触发多返回栈逻辑,id不匹配时只会执行普通的navigate操作,销毁上一个Fragment实例。 - 自定义了
BottomNavigationView.OnNavigationItemSelectedListener拦截点击事件,手动调用navController.navigate()时没有传入正确的NavOptions,绕过了内置的多返回栈逻辑。 - 布局文件中NavHostFragment配置错误,缺失必要属性。
- 导航图中给顶级tab目的地配置了错误的
popUpTo、launchSingleTop属性,破坏了默认返回栈规则。 - 项目存在依赖冲突,实际运行的Navigation库版本低于2.4.0,多返回栈逻辑根本没有生效。
按以下顺序逐一排查即可解决:
核对id匹配性
打开res/menu/目录下的底部导航菜单文件,逐一核对每个item的id,和res/navigation/下导航图中对应Fragment的android:id完全一致,大小写、下划线、命名不能有任何差异。注意:哪怕只是item id多了个前缀、少了个后缀,都会导致匹配失败,走旧的重建逻辑。
移除自定义的导航选中监听
setupWithNavController已经内置了完整的多返回栈切换逻辑,不需要手动给BottomNavigationView设置setOnItemSelectedListener处理跳转。如果确实需要加自定义逻辑(比如埋点),不要自己写navigate逻辑,直接调用NavigationUI的默认处理方法:bottomNavigationView.setOnItemSelectedListener { item -> // 自定义埋点等逻辑写在这里 NavigationUI.onNavDestinationSelected(item, navController) } // 可选:处理重复点击tab回到tab首页的逻辑 bottomNavigationView.setOnItemReselectedListener { item -> val popOptions = NavOptions.Builder() .setPopUpTo(item.itemId, inclusive = false, saveState = true) .build() navController.navigate(item.itemId, null, popOptions) }校验NavHostFragment的XML配置
确认你布局中NavHostFragment的配置符合规范,参考如下正确配置:<androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_container" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph" />重点确认两个属性不能缺:
android:name="androidx.navigation.fragment.NavHostFragment"、app:defaultNavHost="true"。清理导航图的冗余配置
打开导航图文件,删除三个顶级目的地(titleScreen、leaderboard、register)之间手动配置的跳转action,也不要给这些action加多余的popUpTo、launchSingleTop配置,默认逻辑会自动处理返回栈保存。排查依赖版本冲突
在项目根目录执行./gradlew app:dependencies查看依赖树,确认所有navigation相关依赖的版本都是2.5.0。如果存在其他库传递引入的低版本navigation依赖,在app模块的build.gradle中强制指定版本:configurations.all { resolutionStrategy.force 'androidx.navigation:navigation-fragment-ktx:2.5.0' resolutionStrategy.force 'androidx.navigation:navigation-ui-ktx:2.5.0' }
完成以上排查修改后,底部tab切换时就会保留每个tab的返回栈,Fragment不会再重复创建,输入框状态、列表滚动位置都会自动保留。
内容的提问来源于stack exchange,提问作者Fortray

