04-Fragment切换与事务
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 内存。
- 较少使用,实际项目中不如
replace、hide/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。