You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Navigation组件:切换底部标签时如何不销毁Fragment视图?

解决BottomNavBar结合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仅被隐藏而非移除,从而保留实例(视图仍会销毁,但可通过前两种方案恢复状态):

  1. 实现自定义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()
        }
    }
}
  1. 创建自定义NavHostFragment注册该Navigator:
class KeepStateNavHostFragment : NavHostFragment() {
    override fun onCreateNavController(navController: NavController) {
        super.onCreateNavController(navController)
        navController.navigatorProvider.addNavigator(
            KeepStateFragmentNavigator(
                requireContext(),
                childFragmentManager,
                id
            )
        )
    }
}
  1. 在布局中替换默认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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 17:23:13