Fragment
介绍
Fragment 是 Android 应用中的一个模块化组件,它代表 Activity 中的一部分用户界面或行为。你可以把它想象成一个“小 Activity”。
可以把它理解为:
- Activity 是一个大的“舞台”
- FragmentManager 是“舞台经理”
- Fragment 是“演员”
主要特点:
- 模块化: Fragment 可以将复杂的 Activity UI 拆分成可重用、独立的模块。
- 可重用性: 可以在多个 Activity 中重复使用同一个 Fragment。
- 生命周期: Fragment 有自己的生命周期,但它的生命周期受其所在的 Activity 生命周期影响。
- 灵活性: 它们是创建灵活的用户界面(尤其是支持不同屏幕尺寸,如手机和平板)的关键。例如,在平板上,你可能将两个 Fragment 并排显示在一个 Activity 中;而在手机上,你可能在两个不同的 Activity 中分别显示它们。
FragmentManager - Fragment 的管理器
FragmentManager 是一个用于管理应用中的 Fragment 的类。它是 Fragment 的“管家”。
主要作用:
- 执行事务(Transactions): 使用 FragmentManager 来执行添加 (add)、移除 (remove)、替换 (replace) 等 Fragment 操作。
- 查找 Fragment: 可以通过 ID 或 Tag 查找当前 Activity 中已存在的 Fragment。
- 管理返回堆栈(Back Stack): 允许用户通过按下返回按钮回退到前一个 Fragment 状态。
如何使用
通常使用getSupportFragmentManager()或在 Kotlin 中直接使用supportFragmentManager属性来获取一个FragmentManager实例。
当你要对 Fragment 做任何操作,都必须先开启一个事务(Transaction):
val transaction = supportFragmentManager.beginTransaction()之后你就可以链式调用一系列操作,比如:
| 方法 | 作用 |
|---|---|
.add(containerId, fragment) | 添加 Fragment |
.replace(containerId, fragment) | 替换已有 Fragment |
.remove(fragment) | 移除 Fragment |
.hide(fragment) / .show(fragment) | 隐藏 / 显示 Fragment |
.addToBackStack(null) | 加入返回栈,按返回键可以回退 |
.commit() | 异步提交事务 |
.commitNow() | 立即执行(同步提交) |
例子
在 BottomSheet 里有个容器:
<FrameLayout
android:id="@+id/bottom_sheet_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />然后在 Activity 里这样写:
val fragment = ChatFragment()
supportFragmentManager.beginTransaction()
.replace(R.id.bottom_sheet_fragment_container, fragment) // 放进去
.commitNowAllowingStateLoss() // 立即生效replace():会清空容器内的旧 Fragment 并放入新 Fragment
commitNowAllowingStateLoss():表示立即执行事务,不等主线程空闲(UI 立即刷新)
事务执行的三种方式对比
| 方法 | 特点 | 适合场景 |
|---|---|---|
.commit() | 异步执行,下次主线程空闲时执行 | 一般 UI 切换 |
.commitNow() | 立即执行,不可放入返回栈 | 需要立即显示内容 |
.commitAllowingStateLoss() | 避免 “state loss” 崩溃 | 生命周期边界执行(不推荐) |
.commitNowAllowingStateLoss() | 立即执行 + 避免崩溃 | 确保立即展示 Fragment(当前这种场景很合适) |
FragmentTransaction 的重要方法
val transaction = supportFragmentManager.beginTransaction()
// 1. add() - 添加新的 Fragment
transaction.add(R.id.container, MyFragment(), "fragment_tag")
// 参数3: tag(可选),用于以后通过 tag 查找 Fragment
// 2. replace() - 替换当前 Fragment
transaction.replace(R.id.container, NewFragment())
// 会移除容器中所有现有的 Fragment,然后添加新的
// 3. remove() - 移除 Fragment
val fragment = supportFragmentManager.findFragmentByTag("fragment_tag")
if (fragment != null) {
transaction.remove(fragment)
}
// 4. show() / hide() - 显示/隐藏 Fragment(不会销毁)
val fragment = supportFragmentManager.findFragmentByTag("fragment_tag")
transaction.hide(fragment) // 隐藏,但实例和视图都保留
transaction.show(fragment) // 重新显示
// 5. detach() / attach() - 分离/重新附加
transaction.detach(fragment) // 分离:销毁视图,但实例保留
transaction.attach(fragment) // 重新附加:重新创建视图
// 6. addToBackStack() - 添加到返回栈
transaction.addToBackStack(null) // null 表示不需要 tag
transaction.addToBackStack("transaction_name")
// 7. setCustomAnimations() - 设置动画
transaction.setCustomAnimations(
enterAnim, // 进入动画资源
exitAnim, // 退出动画资源
popEnterAnim, // 返回时进入动画
popExitAnim // 返回时退出动画
)
// 8. setTransition() - 设置过渡效果
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
// 9. commit() - 提交事务
transaction.commit() // 立即提交,但可能在主线程执行
transaction.commitNow() // 立即同步执行
transaction.commitAllowingStateLoss() // 允许状态丢失时提交Activity 和 Fragment 各有自己的 FragmentManager
| 环境 | 获取方式 | 说明 |
|---|---|---|
| 在 Activity 中 | supportFragmentManager | 管理 Activity 内的所有 Fragment |
| 在 Fragment 中 | childFragmentManager | 管理当前 Fragment 内部嵌套的子 Fragment |
代码示例
方式一
步骤1:Activity 布局中添加 Fragment 容器
<!-- activity_main.xml -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />步骤2:创建 Fragment
// FirstFragment.kt
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// 创建 Fragment 的视图
val view = inflater.inflate(R.layout.fragment_first, container, false)
// 设置按钮点击事件
view.findViewById<Button>(R.id.btn_go_to_second).setOnClickListener {
// 导航到第二个 Fragment
(requireActivity() as MainActivity).navigateToSecondFragment()
}
return view
}
}步骤3:Activity 中管理 Fragment
// MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 如果 savedInstanceState 为 null,说明是第一次创建
// 防止旋转屏幕等配置变更时重复添加
if (savedInstanceState == null) {
// 初始化显示第一个 Fragment
showFirstFragment()
}
}
private fun showFirstFragment() {
// 1. 获取 FragmentManager
val fragmentManager = supportFragmentManager
// 2. 开始一个事务
val transaction = fragmentManager.beginTransaction()
// 3. 添加 FirstFragment 到容器
transaction.add(R.id.fragment_container, FirstFragment())
// 4. 这里不添加到返回栈,因为这是第一个 Fragment
// 如果添加到返回栈,按返回键会退出应用而不是返回上一个Fragment
// 5. 提交事务
transaction.commit()
}
// 从 FirstFragment 调用的方法
fun navigateToSecondFragment() {
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
// 替换为 SecondFragment
transaction.replace(R.id.fragment_container, SecondFragment())
// 关键:添加到返回栈
// 这样按返回键时可以回到 FirstFragment
transaction.addToBackStack("first_to_second")
// 可以设置转场动画
transaction.setCustomAnimations(
android.R.anim.slide_in_left, // 进入动画
android.R.anim.slide_out_left, // 退出动画
android.R.anim.slide_in_right, // 返回进入动画
android.R.anim.slide_out_right // 返回退出动画
)
transaction.commit()
}
}方式二
可以通过新建 Basic Views Activity 的方式来创建一个基础视图,这个视图带有两个 Fragment。
## 返回栈的工作原理
### **返回栈存储的是什么?**
Fragment返回栈 = 栈结构,存储 FragmentTransaction 记录
每个记录包含:
- 执行了什么操作(add/replace/remove)
- 操作了哪个Fragment
- 状态信息
操作示例:
// 初始状态:栈为空
// 容器为空
// 1. 添加 FragmentA
supportFragmentManager.beginTransaction()
.add(R.id.container, FragmentA())
.addToBackStack("add_A") // 记录1入栈
.commit()
// 栈:[添加A]
// 显示:FragmentA
// 2. 替换为 FragmentB
supportFragmentManager.beginTransaction()
.replace(R.id.container, FragmentB())
.addToBackStack("replace_B") // 记录2入栈
.commit()
// 栈:[添加A, 替换为B]
// 显示:FragmentB
// 3. 用户按返回键
// 系统:弹出栈顶记录[替换为B]
// 执行反向操作:移除B,恢复A
// 栈:[添加A]
// 显示:FragmentA
// 4. 用户再按返回键
// 系统:弹出栈顶记录[添加A]
// 执行反向操作:移除A
// 栈:[]
// 容器为空(可能显示Activity的空白布局)Fragment 生命周期与返回栈
添加到返回栈时的生命周期变化
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("Lifecycle", "onCreate: Fragment实例创建")
// 实例创建,只调用一次(除非完全销毁)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?): View {
Log.d("Lifecycle", "onCreateView: 创建视图")
return inflater.inflate(R.layout.my_fragment, container, false)
}
override fun onDestroyView() {
super.onDestroyView()
Log.d("Lifecycle", "onDestroyView: 销毁视图")
// Fragment进入返回栈时调用
// 视图被移除,但Fragment实例还在
}
override fun onDestroy() {
super.onDestroy()
Log.d("Lifecycle", "onDestroy: 销毁实例")
// Fragment被完全移除(没有添加到返回栈)时调用
}
}QA
Q:对比 Activity 和 Fragment,Fragment 的优势是什么?
Fragment 比 Activity 灵活得多。主要体现在:
// Activity:要么显示,要么不显示(二选一)
startActivity(Intent(this, ActivityB::class.java))
// ActivityA 被完全推到后台
// Fragment:多种组合方式
supportFragmentManager.beginTransaction()
.add(R.id.container, FragmentB()) // 添加(不隐藏A)
// 或
.replace(R.id.container, FragmentB()) // 替换(移除A)
// 或
.hide(fragmentA).add(R.id.container, FragmentB()) // 隐藏A,添加B
// 或
.add(R.id.left_container, FragmentA()) // 多容器布局
.add(R.id.right_container, FragmentB())
.commit()- 共享同一个 Activity:多个 Fragment 可以同时存在于一个 Activity 中
- 动态组合:运行时可以添加、移除、替换、隐藏、显示
- 生命周期分离:视图和实例可以独立管理
Activity:固定窗口
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 一次性设置,通常不改变
setContentView(R.layout.activity_main)
}
}- 视图生命周期 = Activity 生命周期
- 视图只创建一次(除非配置变更)
Fragment:动态内容块
class ContentFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?): View {
// 可能被调用多次
// 根据当前状态返回不同的视图
return when (currentMode) {
MODE_LIST -> inflater.inflate(R.layout.list_mode, container, false)
MODE_DETAIL -> inflater.inflate(R.layout.detail_mode, container, false)
MODE_EDIT -> inflater.inflate(R.layout.edit_mode, container, false)
}
}
}Q:创建出来以后有一个nav_graph.xml是干什么的?
nav_graph.xml是 Android Jetpack Navigation 组件的导航图文件,用于声明式导航。
<!-- nav_graph.xml 示例 -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/firstFragment">
<!-- 目的地(通常是Fragment) -->
<fragment
android:id="@+id/firstFragment"
android:name="com.example.FirstFragment"
android:label="First Fragment">
<!-- 动作(导航路径) -->
<action
android:id="@+id/action_first_to_second"
app:destination="@id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.example.SecondFragment"
android:label="Second Fragment" />
</navigation>Q:Navigation 组件的作用?
A:
- 声明式导航:在 XML 中定义导航结构
- 可视化:Android Studio 有导航图编辑器
- 类型安全:通过 Safe Args 插件确保参数类型安全
- 简化代码:自动处理返回栈
Q:Navigation 与addToBackStack()的区别?
传统方式:FragmentManager+addToBackStack()
缺点:
- 事务逻辑分散在代码中
- 容易出错(忘记 addToBackStack)
- 类型不安全(通过 Bundle 传参)
- 没有可视化工具
// 手动管理 Fragment 事务
supportFragmentManager.beginTransaction()
.replace(R.id.container, SecondFragment())
.addToBackStack("first_to_second") // 手动添加
.commit()
Navigation 组件方式
Navigation 组件是现代化的导航框架,自动管理返回栈
自动:
- 添加到返回栈(可在 XML 中配置是否添加)
- 类型安全传参(Safe Args)
- 统一管理导航逻辑
// 通过导航图自动管理
findNavController().navigate(R.id.action_first_to_second)| 方面 | FragmentManager +** **addToBackStack() | Navigation Component |
|---|---|---|
| 管理方式 | 命令式(代码控制) | 声明式(XML 定义) |
| 返回栈 | 手动控制 | 自动管理(可配置) |
| 可视化 | 无 | Android Studio 导航图 |
| 类型安全 | 无(Bundle 传参) | 有(Safe Args) |
| 深层链接 | 手动处理 | 内置支持 |
| 动画 | 手动设置 | XML 中定义 |
Q:为什么要把 Fragment 分为视图和实例?
A:如下原因。
内存优化
// 假设 Fragment 显示图片列表
class ImageGalleryFragment : Fragment() {
// 轻量级:只保存数据引用
private val imageUrls: List<String> = loadImageUrls()
override fun onCreateView(): View {
// 重量级:创建包含图片的视图
// 当 Fragment 不可见时,销毁视图释放内存
return createImageViewPager(imageUrls)
}
override fun onDestroyView() {
super.onDestroyView()
// 释放图片资源,但 imageUrls 还保留着
}
}状态保持与视图重建分离
class CounterFragment : Fragment() {
// 状态:保存在 Fragment 实例中
private var count = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val countTextView = view.findViewById<TextView>(R.id.count_text)
val incrementButton = view.findViewById<Button>(R.id.increment_button)
// 恢复显示状态
countTextView.text = "Count: $count"
incrementButton.setOnClickListener {
count++
countTextView.text = "Count: $count"
}
}
// 旋转屏幕后:
// 1. Fragment 实例保持,count 值保留
// 2. 视图重新创建
// 3. onViewCreated 中重新显示 count 的当前值
}更新: 2026-01-15 17:35:17
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/nk7gwyvg0b4krkgb