Fragment 切换与事务

1. FragmentTransaction 是什么

对 Fragment 的添加、移除、替换、隐藏、显示、入栈等操作,都需要通过 FragmentTransaction 完成。

常见写法:

supportFragmentManager.commit {
    setReorderingAllowed(true)
    replace(R.id.container, DetailFragment())
    addToBackStack(null)
}

事务可以理解为:

把一组 Fragment 操作打包,然后一次性提交给 FragmentManager 执行。

2. add / remove

add

add() 会把 Fragment 添加到容器中。

supportFragmentManager.commit {
    add(R.id.container, HomeFragment(), "home")
}

特点:

  • Fragment 实例被加入 FragmentManager。
  • View 被创建并添加到容器。
  • 如果不隐藏旧 Fragment,多个 Fragment 的 View 可能叠在一起。

remove

remove() 会移除 Fragment。

val fragment = supportFragmentManager.findFragmentByTag("home")
supportFragmentManager.commit {
    if (fragment != null) remove(fragment)
}

特点:

  • Fragment 会走 onDestroyView()
  • 如果不在返回栈中,通常还会走 onDestroy()onDetach()
  • 适合不再需要该 Fragment 的场景。

3. replace

replace() 可以理解为:

先移除指定容器里已有的 Fragment,再添加新的 Fragment。

示例:

supportFragmentManager.commit {
    replace(R.id.container, DetailFragment())
    addToBackStack("detail")
}

适合场景:

  • 普通页面跳转。
  • 当前页面不需要常驻内存。
  • 需要配合返回栈返回上一个页面。

注意:replace() 不是“不能算切换方式”,它是实际项目中非常常见的切换方式,只是底层效果接近 remove + add。

4. hide / show

hide()show() 只改变已添加 Fragment 的 View 可见性,不会销毁 Fragment 的 View。

supportFragmentManager.commit {
    hide(currentFragment)
    show(targetFragment)
}

适合场景:

  • 首页底部 Tab。
  • 页面数量少。
  • 希望切换回来时保留页面状态和滚动位置。

典型封装:

fun switchTab(target: Fragment, tag: String) {
    val manager = supportFragmentManager
    val current = manager.primaryNavigationFragment

    manager.commit {
        if (current != null) hide(current)

        val existing = manager.findFragmentByTag(tag)
        if (existing == null) {
            add(R.id.container, target, tag)
            setPrimaryNavigationFragment(target)
        } else {
            show(existing)
            setPrimaryNavigationFragment(existing)
        }
    }
}

优点:切换快,View 状态保留。
缺点:多个 Fragment 的 View 都常驻内存,不适合页面很多或页面很重的情况。

5. detach / attach

detach() 会把 Fragment 从 UI 上分离,并销毁它的 View 层级,但 Fragment 仍由 FragmentManager 管理。

supportFragmentManager.commit {
    detach(fragment)
}

attach() 会重新附加 Fragment,并重新创建 View。

supportFragmentManager.commit {
    attach(fragment)
}

特点:

操作 Fragment 实例 View
hide() 保留 保留,只是不可见
detach() 保留 销毁
remove() 通常销毁 销毁

适合场景:

  • 想保留 Fragment 实例状态,但释放 View 内存。
  • 较少使用,实际项目中不如 replacehide/show 常见。

6. addToBackStack

如果事务调用了 addToBackStack(),用户按返回键时可以回退这次 Fragment 事务。

supportFragmentManager.commit {
    replace(R.id.container, DetailFragment())
    addToBackStack("detail")
}

不加入返回栈:

A replace B 后,A 通常被销毁;按返回键 Activity 退出。

加入返回栈:

A replace B,并 addToBackStack;按返回键 B 出栈,A 恢复。

7. commit / commitNow / commitAllowingStateLoss

方法 特点 使用建议
commit() 异步提交,主线程稍后执行 最常用
commitNow() 立即执行,不允许加入返回栈 需要立即拿到结果时使用
commitAllowingStateLoss() 允许状态丢失 尽量避免,只在能接受 UI 状态丢失时使用

为什么 commitAllowingStateLoss() 要谨慎?

因为 Activity 保存状态之后再提交 Fragment 事务,系统恢复页面时可能恢复不到这次事务,导致 UI 状态不一致。

8. 选择建议

场景 推荐方式
普通详情页跳转 replace + addToBackStack 或 Navigation
首页底部 Tab add + hide/show 或 Navigation 多返回栈
页面很多的滑动分页 ViewPager2 + FragmentStateAdapter
想释放 View 但保留实例 detach/attach
新项目统一页面导航 Jetpack Navigation

9. 面试回答模板

问题:Fragment 有哪些切换方式?区别是什么?

可以这样回答:

常见有 add/remove、replace、hide/show、detach/attach。replace 底层效果接近先 remove 再 add,适合普通页面跳转;hide/show 只是改变 View 可见性,不销毁 View,适合底部 Tab;detach 会销毁 View 但保留 Fragment 由 FragmentManager 管理,attach 时重新创建 View;remove 则会把 Fragment 从 FragmentManager 中移除。实际项目中如果是页面导航,优先考虑 Jetpack Navigation。