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

如何使用Navigation Component保留Fragment避免重复销毁重建?

保留侧边抽屉Fragment实例,避免重复销毁重建的解决方案

核心结论

可以直接保留Fragment实例,无需每次切换都重建。你的实现和Google官方示例的核心差异在于导航宿主的管理方式——默认单NavHost切换导航图的方式会触发Fragment重建,而多NavHost切换显示隐藏的模式能完整保留实例状态。

为什么当前实现会导致Fragment重建

你当前采用的是单个NavHostFragment嵌套多个navigation节点的方案,使用setupWithNavController绑定抽屉菜单时,每次点击菜单项都会触发navigate()方法:

  • 切换新菜单时,当前Fragment会被销毁,新目标的Fragment实例被创建并加入回退栈
  • 按下返回键时,回退栈中的实例出栈,起始Fragment会被重新创建,而非复用之前的实例

这种单NavHost导航模式下,Navigation组件默认不会保留已离开的Fragment实例,而是通过重建来恢复状态。

基于官方示例的修改方案

要实现实例保留,需采用多NavHostFragment + 切换显示隐藏的模式,具体步骤如下:

1. 修改Activity布局,添加多个NavHostFragment

替换原来的单个FragmentContainerView,为每个抽屉菜单项创建独立的导航宿主,初始仅显示起始项:

<!-- activity_sidenav_default.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <!-- Home导航宿主 -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/home_nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_home"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- Gallery导航宿主 -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/gallery_nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:navGraph="@navigation/nav_gallery"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="gone" />

    <!-- Slideshow导航宿主 -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/slideshow_nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:navGraph="@navigation/nav_slideshow"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>

2. 拆分原导航图为独立子导航图

将原来的嵌套导航图拆分为单个独立文件,每个文件对应一个抽屉项的导航逻辑:

  • nav_home.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@+id/frag_home">

    <fragment
        android:id="@+id/frag_home"
        android:name="work.curioustools.fragmentpracticalsamples.side_navigation.HomeFragment"
        android:label="@string/menu_home"
        tools:layout="@layout/fragment_side_navigation" />
</navigation>
  • nav_gallery.xml和nav_slideshow.xml同理,分别对应各自的Fragment配置。

3. 修改Activity代码,手动处理菜单切换与导航

不再使用setupWithNavController,而是手动监听抽屉菜单点击,切换对应NavHost的显示状态,并管理AppBar和回退栈:

class SideNavigationDrawerDefaultActivity : AppCompatActivity() {
    private val binding by lazy { ActivitySideNavigationDefaultBinding.inflate(layoutInflater) }
    private val navHostMap = mutableMapOf<Int, NavHostFragment>()
    private var currentNavHostId: Int = R.id.home_nav_host

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        setSupportActionBar(binding.toolbar)
        // 初始化所有导航宿主
        navHostMap[R.id.nav_home] = supportFragmentManager.findFragmentById(R.id.home_nav_host) as NavHostFragment
        navHostMap[R.id.nav_gallery] = supportFragmentManager.findFragmentById(R.id.gallery_nav_host) as NavHostFragment
        navHostMap[R.id.nav_slideshow] = supportFragmentManager.findFragmentById(R.id.slideshow_nav_host) as NavHostFragment

        // 手动处理抽屉菜单点击事件
        binding.navView.setNavigationItemSelectedListener { menuItem ->
            val targetNavHostId = when (menuItem.itemId) {
                R.id.nav_home -> R.id.home_nav_host
                R.id.nav_gallery -> R.id.gallery_nav_host
                R.id.nav_slideshow -> R.id.slideshow_nav_host
                else -> currentNavHostId
            }

            if (targetNavHostId != currentNavHostId) {
                // 切换导航宿主的显示/隐藏状态
                supportFragmentManager.beginTransaction()
                    .hide(supportFragmentManager.findFragmentById(currentNavHostId)!!)
                    .show(supportFragmentManager.findFragmentById(targetNavHostId)!!)
                    .commit()
                currentNavHostId = targetNavHostId
                // 更新标题与选中菜单项
                supportActionBar?.title = menuItem.title
                binding.navView.setCheckedItem(menuItem.itemId)
            }
            // 关闭抽屉
            binding.drawerLayout.closeDrawers()
            true
        }

        // 初始化默认标题
        supportActionBar?.title = binding.navView.menu.findItem(R.id.nav_home).title
        binding.fab.setOnClickListener { snack("hello", it) }
    }

    // 处理返回键逻辑
    override fun onBackPressed() {
        val currentNavController = navHostMap.values.first { it.isVisible }.navController
        if (!currentNavController.popBackStack()) {
            super.onBackPressed()
        }
    }

    override fun onSupportNavigateUp(): Boolean {
        val currentNavController = navHostMap.values.first { it.isVisible }.navController
        return currentNavController.navigateUp() || super.onSupportNavigateUp()
    }
}

4. 确保菜单ID匹配

确认menu_side_navigation.xml中的菜单项ID为R.id.nav_home、R.id.nav_gallery、R.id.nav_slideshow,与Activity中的逻辑对应。

关键原理

通过为每个抽屉菜单项分配独立的NavHostFragment,切换时仅隐藏当前宿主、显示目标宿主,而非销毁重建Fragment实例。每个NavHostFragment会维护自己的导航栈和Fragment状态,从而实现实例的完整保留。

内容的提问来源于stack exchange,提问作者ansh sachdeva

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 13:07:13