Android Navigation组件:切换底部标签时如何不销毁Fragment视图?
方案1:利用Fragment的onSaveInstanceState保存视图状态
对于滚动位置这类视图级别的状态,可以通过Fragment的状态保存回调持久化,在视图重建时恢复:
class MyListFragment : Fragment() { private var recyclerScrollState: Parcelable? = null private lateinit var binding: FragmentMyListBinding override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = FragmentMyListBinding.inflate(inflater, container, false) return binding.root } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // 保存RecyclerView的滚动状态 recyclerScrollState = binding.recyclerView.layoutManager?.onSaveInstanceState() outState.putParcelable("RECYCLER_SCROLL_STATE", recyclerScrollState) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 初始化RecyclerView适配器与数据 initRecyclerView() // 恢复滚动状态 savedInstanceState?.let { recyclerScrollState = it.getParcelable("RECYCLER_SCROLL_STATE") binding.recyclerView.layoutManager?.onRestoreInstanceState(recyclerScrollState) } } private fun initRecyclerView() { // 适配器初始化逻辑 binding.recyclerView.adapter = MyListAdapter() binding.recyclerView.layoutManager = LinearLayoutManager(context) } }
方案2:用ViewModel持久化数据与状态
ViewModel的生命周期独立于Fragment的视图生命周期,不会随视图销毁而销毁,适合保存需要跨视图重建保留的数据(比如列表数据源、筛选条件):
// 定义ViewModel class MyListViewModel : ViewModel() { // 保存列表数据源 val listData = MutableLiveData<List<MyItem>>() // 保存滚动状态 var recyclerScrollState: Parcelable? = null } // 在Fragment中使用 class MyListFragment : Fragment() { private lateinit var binding: FragmentMyListBinding private val viewModel: MyListViewModel by viewModels() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = FragmentMyListBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initRecyclerView() // 观察数据更新列表 viewModel.listData.observe(viewLifecycleOwner) { data -> (binding.recyclerView.adapter as MyListAdapter).submitList(data) // 数据更新后恢复滚动位置 binding.recyclerView.layoutManager?.onRestoreInstanceState(viewModel.recyclerScrollState) } } override fun onPause() { super.onPause() // 离开页面时保存滚动状态到ViewModel viewModel.recyclerScrollState = binding.recyclerView.layoutManager?.onSaveInstanceState() } private fun initRecyclerView() { binding.recyclerView.adapter = MyListAdapter() binding.recyclerView.layoutManager = LinearLayoutManager(context) } }
方案3:自定义FragmentNavigator,保留Fragment实例
默认Navigation组件切换标签时会移除旧Fragment,我们可以自定义Navigator,让Fragment仅被隐藏而非移除,从而保留实例(视图仍会销毁,但可通过前两种方案恢复状态):
- 实现自定义Navigator:
class KeepStateFragmentNavigator( context: Context, fragmentManager: FragmentManager, containerId: Int ) : FragmentNavigator(context, fragmentManager, containerId) { override fun navigate( entries: List<NavBackStackEntry>, navOptions: NavOptions?, navigatorExtras: Navigator.Extras? ) { val backStack = fragmentManager.backStack // 清理重复的返回栈条目 while (backStack.isNotEmpty()) { val entry = backStack.last() if (entries.any { it.destination.id == entry.id }) { fragmentManager.popBackStack(entry.id, FragmentManager.POP_BACK_STACK_INCLUSIVE) } else { break } } entries.forEach { entry -> val tag = entry.destination.id.toString() val existingFragment = fragmentManager.findFragmentByTag(tag) val transaction = fragmentManager.beginTransaction() // 隐藏所有已添加的Fragment fragmentManager.fragments.forEach { transaction.hide(it) } if (existingFragment != null) { transaction.show(existingFragment) } else { val fragment = createFragment(entry.destination, entry.arguments) transaction.add(containerId, fragment, tag) } // 设置当前显示的Fragment为导航主Fragment transaction.setPrimaryNavigationFragment(fragmentManager.findFragmentByTag(tag)) transaction.commit() } } }
- 创建自定义NavHostFragment注册该Navigator:
class KeepStateNavHostFragment : NavHostFragment() { override fun onCreateNavController(navController: NavController) { super.onCreateNavController(navController) navController.navigatorProvider.addNavigator( KeepStateFragmentNavigator( requireContext(), childFragmentManager, id ) ) } }
- 在布局中替换默认NavHostFragment:
<fragment android:id="@+id/nav_host_fragment" android:name="com.yourpackage.KeepStateNavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:navGraph="@navigation/nav_graph" />
注意事项
- 优先使用ViewModel方案,符合Jetpack组件设计规范,避免Fragment实例保留带来的内存泄漏风险
- 自定义Navigator适合需要保留Fragment实例的复杂场景,但需注意返回栈的处理逻辑,避免导航异常
- 滚动位置这类视图状态,结合
onSaveInstanceState和ViewModel可以更可靠地恢复
内容的提问来源于stack exchange,提问作者Konstantin Konopko
相关产品推荐
相关产品推荐

