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。

1768462150296-cc862bb4-ed58-4942-943d-ad7b557d049e.png ## 返回栈的工作原理 ### **返回栈存储的是什么?** Fragment返回栈 = 栈结构,存储 FragmentTransaction 记录

每个记录包含:

  1. 执行了什么操作(add/replace/remove)
  2. 操作了哪个Fragment
  3. 状态信息

操作示例:

// 初始状态:栈为空
// 容器为空
 
// 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()

缺点:

  1. 事务逻辑分散在代码中
  2. 容易出错(忘记 addToBackStack)
  3. 类型不安全(通过 Bundle 传参)
  4. 没有可视化工具
// 手动管理 Fragment 事务
supportFragmentManager.beginTransaction()
    .replace(R.id.container, SecondFragment())
    .addToBackStack("first_to_second")  // 手动添加
    .commit()
 

Navigation 组件方式

Navigation 组件是现代化的导航框架,自动管理返回栈

自动:

  1. 添加到返回栈(可在 XML 中配置是否添加)
  2. 类型安全传参(Safe Args)
  3. 统一管理导航逻辑
// 通过导航图自动管理
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


相关笔记