【Android基础】Fragment组件进阶

本文介绍了Fragment组件的进阶知识,包括Fragment的生命周期、Fragment间的通信方式等。
Fragment 是 Android UI 开发中一个非常重要的组件,用于构建模块化、可复用且灵活的用户界面。
Fragment 可以被视为一个Activity 的一部分或行为。它拥有自己的生命周期、布局和输入事件,但它必须托管在一个 Activity 中。一个 Activity 可以包含一个或多个 Fragment,也可以在不同的 Activity 中复用同一个 Fragment。
Fragment 的主要作用
- 模块化 UI: 可以将一个复杂的用户界面分解成独立的、可管理的模块。
- UI 可复用性: 可以在不同的 Activity 或同一 Activity 的不同配置(如横竖屏)中复用 Fragment。
- 适应不同屏幕尺寸: 尤其在平板电脑等大屏幕设备上,可以同时显示多个 Fragment,例如列表-详情布局(List-Detail Flow)。
- 简化 Activity 代码: 将 UI 逻辑和行为从 Activity 中分离出来,使 Activity 变得更轻量和专注于协调。
- 支持回退栈: 可以像 Activity 一样管理 Fragment 的回退栈,实现前进和后退导航。
生命周期
Fragment 的生命周期与它所依附的 Activity 的生命周期紧密相关。理解这些回调方法对于正确管理 Fragment 的状态至关重要。
以下是 Fragment 生命周期中几个关键的方法及其大致顺序:
onAttach(): 当 Fragment 与 Activity 关联时调用。此时可以获取到Context对象。onCreate(): Fragment 被创建时调用。在这里进行非 UI 的初始化,如变量设置、数据加载等。onCreateView(): 创建 Fragment 的用户界面(View)。在这里膨胀(inflate)布局并返回根视图。onViewCreated():onCreateView()返回后调用。在这里可以初始化 View 组件,设置监听器等。onActivityCreated(): 当宿主 Activity 的onCreate()方法完成时调用。可以在这里执行依赖于 Activity 已创建的代码。onStart(): Fragment 可见时调用。onResume(): Fragment 获得焦点并可与用户交互时调用。onPause(): Fragment 失去焦点,但仍然部分可见时调用(例如,另一个 Fragment 覆盖了它)。onStop(): Fragment 不再可见时调用。onDestroyView(): Fragment 的视图被移除时调用。在这里释放与 View 相关的资源。onDestroy(): Fragment 实例被销毁时调用。在这里释放所有非 View 相关的资源。onDetach(): Fragment 与 Activity 解除关联时调用。
使用流程
1. 创建 Fragment
创建一个继承自 androidx.fragment.app.Fragment 的 Java/Kotlin 类,并通常重写 onCreateView() 方法来提供其布局:
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Fragment 初始化逻辑
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 膨胀 Fragment 的布局
return inflater.inflate(R.layout.fragment_my, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 初始化 View 组件
// val myTextView = view.findViewById<TextView>(R.id.myTextView)
// myTextView.text = "Hello from Fragment!"
}
}
对应的 fragment_my.xml 布局文件:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/myTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="My Fragment Content"
android:textSize="24sp"
android:layout_gravity="center" />
</FrameLayout>
2. 将 Fragment 添加到 Activity
有两种主要方式将 Fragment 添加到 Activity 中:
a. 在布局 XML 中声明
你可以在 Activity 的布局 XML 文件中直接声明一个 Fragment。这是 静态添加 Fragment 的方式。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/my_static_fragment"
android:name="com.example.yourapp.MyFragment" // 完整的 Fragment 类名
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
这种方式下,Fragment 的生命周期会与 Activity 的生命周期紧密耦合,并且在 Activity 创建时就被实例化。
b. 运行时动态添加(推荐)
通过 FragmentManager 和 FragmentTransaction 在 Activity 运行时动态添加、移除、替换或显示/隐藏 Fragment。这是最常用的方式,因为它提供了更大的灵活性。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 检查 Fragment 是否已经添加,避免重复添加(例如在配置变化后)
if (savedInstanceState == null) {
val fragmentManager: FragmentManager = supportFragmentManager
val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
val myFragment = MyFragment()
// 将 Fragment 添加到一个容器视图中 (例如一个 FrameLayout)
fragmentTransaction.add(R.id.fragment_container, myFragment)
// fragmentTransaction.addToBackStack(null) // 可选:添加到回退栈
fragmentTransaction.commit()
}
}
}
初始化添加时最好是先行检查一下
savedInstanceState是否为null,避免重复添加 Fragment。如果是系统的配置变更,如语言和主题,我们知道Activity会自动重建,而FagmentManager 会在 Activity 重建时,自动恢复那些在 Activity 被销毁前已经存在的 Fragment 实例。如果此时又调用了fragmentTransaction.add()方法添加 Fragment,就会导致重复添加,引发异常,界面内容可能会重叠显示多个fragment。
对应的 activity_main.xml 布局需要一个用于容纳 Fragment 的容器:
<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" />
Fragment 的通信
由于 Fragment 之间是独立的,它们之间以及与宿主 Activity 之间需要明确的通信机制。
Fragment 到 Activity:
- 推荐方式: 定义一个接口,让 Activity 实现该接口。Fragment 通过
onAttach()获取 Activity 实例并将其转换为接口类型,然后调用接口方法。
// Fragment class MyFragment : Fragment() { interface OnMessageListener { fun onMessageFromFragment(message: String) } private var listener: OnMessageListener? = null override fun onAttach(context: Context) { super.onAttach(context) if (context is OnMessageListener) { listener = context } else { throw RuntimeException("$context must implement OnMessageListener") } } // ... 某个事件触发时 fun sendMessage() { listener?.onMessageFromFragment("Hello from Fragment!") } override fun onDetach() { super.onDetach() listener = null } } // Activity class MainActivity : AppCompatActivity(), MyFragment.OnMessageListener { override fun onMessageFromFragment(message: String) { Log.d("MainActivity", "Received message: $message") } // ... }- ViewModel (推荐,尤其是 Fragment 间通信): 使用共享的
ViewModel可以非常方便地在 Fragment 和 Activity 之间共享数据和通信,尤其是在导航组件的场景下。
- 推荐方式: 定义一个接口,让 Activity 实现该接口。Fragment 通过
Activity 到 Fragment:
- 调用 Fragment 公开方法: Activity 可以获取 Fragment 实例并直接调用其公共方法。
// Activity fun sendMessageToFragment(message: String) { val myFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as? MyFragment myFragment?.updateText(message) } // Fragment class MyFragment : Fragment() { fun updateText(message: String) { // 更新 TextView } }- 通过 Bundle 传递参数: 在创建 Fragment 实例时,通过
setArguments(Bundle)方法传递参数。
// Activity val args = Bundle().apply { putString("key_message", "Data from Activity") } val myFragment = MyFragment().apply { arguments = args } // Fragment override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val message = arguments?.getString("key_message") // 使用 message }Fragment 到 Fragment:
- 通过宿主 Activity 中转(旧版,不推荐): 一个 Fragment 通知 Activity,Activity 再通知另一个 Fragment。
- 共享 ViewModel (推荐): 多个 Fragment 可以观察同一个
ViewModel中的LiveData,实现数据共享和通信。 - Parent-to-Child FragmentManager (如果存在嵌套 Fragment): 可以通过
getParentFragmentManager()或getChildFragmentManager()获取对应的FragmentManager。 - Navigation Component (推荐): 使用 Android Navigation 组件是处理 Fragment 之间导航和参数传递的现代化且强大的方式。
FragmentTransaction 和回退栈
当使用 FragmentManager 动态管理 Fragment 时,FragmentTransaction 是执行操作(如添加、移除、替换)的批处理API。
add(containerId, fragment): 将一个 Fragment 添加到容器中。remove(fragment): 移除一个 Fragment。replace(containerId, fragment): 移除容器中现有 Fragment,然后添加新的 Fragment。hide(fragment)/show(fragment): 隐藏或显示一个 Fragment,但不会销毁其 View。addToBackStack(name): 将当前FragmentTransaction添加到 Activity 的回退栈中。当用户按返回键时,会依次弹出栈中的 Fragment 事务,回退到之前的 Fragment 状态。commit(): 提交事务。这是异步操作。commitNow(): 提交事务。这是同步操作,但可能会阻塞 UI 线程,除非确定操作很快。通常不推荐。commitAllowingStateLoss(): 提交事务,即使 Activity 状态已保存,允许状态丢失。一般不推荐,除非你清楚这样做的后果。
最佳实践与注意事项
- 避免 Fragment 嵌套过多: 复杂的 Fragment 嵌套会导致生命周期管理变得困难,并可能引入性能问题。
- 使用
setArguments()传递参数: 避免在 Fragment 构造函数中传递参数,因为系统可能会在屏幕旋转等情况下重新创建 Fragment 而不调用自定义构造函数。 - Fragment 应该尽可能独立和可复用: 它们不应该直接依赖于特定的 Activity 类型,而是通过接口或 ViewModel 进行通信。
- 处理配置变更: Fragment 在 Activity 重建时也会重建。确保在
onSaveInstanceState()和onCreate()/onCreateView()中正确保存和恢复 Fragment 的状态。 - 内存泄漏: 注意在
onDestroyView()或onDestroy()中释放不再需要的引用(尤其是对 View 的引用),以避免内存泄漏。例如,清理在onCreateView()中创建的监听器。 getChildFragmentManager()vsgetFragmentManager()/getParentFragmentManager():getParentFragmentManager()(原getFragmentManager()):用于获取管理当前 Fragment 的FragmentManager。getChildFragmentManager():用于获取管理当前 Fragment 内部嵌套 Fragment 的FragmentManager。- 在使用
FragmentContainerView或supportFragmentManager.beginTransaction()动态添加 Fragment 时,请确保使用正确的FragmentManager。
- Navigation Component: 对于复杂的导航和 Fragment 间的通信,强烈推荐使用 Android Jetpack 的 Navigation Component。它简化了 Fragment 的管理、深层链接和安全参数传递。
- View Binding 或 Data Binding: 在 Fragment 中使用 View Binding 或 Data Binding 可以更安全、高效地访问 View 组件,避免
findViewById带来的空指针异常。
常见用例
- 标签页(Tabbed Layouts): 每个标签页内容可以是一个 Fragment。
- 滑动视图(Swipe Views / ViewPager2):
ViewPager2经常与FragmentStateAdapter结合使用,每个页面都是一个 Fragment。 - 大屏幕设备布局: 例如,在平板上,一个 Fragment 显示列表,另一个 Fragment 显示详情。
- 底部导航栏(Bottom Navigation): 每个导航项对应一个 Fragment。
- 向导流(Wizard Flows): 多个 Fragment 按顺序引导用户完成任务。