Fragment

介绍

Fragment是一种可以嵌入在活动中的UI片段,能够让程序更加合理和充分地利用大屏幕的空间,出现的初衷是为了适应大屏幕的平板电脑,可以将其看成一个小型Activity,又称作Activity片段。

使用Fragment可以把屏幕划分成几块,然后进行分组,进行一个模块化管理。Fragment不能够单独使用,需要嵌套在Activity中使用,其生命周期也受到宿主Activity的生命周期的影响

官方定义如下:

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running.

从官方的定义可以得到:

  1. Fragment依赖于Activity,不能独立存在
  2. 一个Activity可以有多个Fragment
  3. 一个Fragment可以被多个Activity重用
  4. Fragment有自己的生命周期,并能接收输入事件
  5. 可以在Activity运行时动态地添加或删除Fragment

Fragment的优势:

  1. 模块化(Modularity):我们不必把所有代码全部写在Activity中,而是把代码写在各自的Fragment中。
  2. 可重用(Reusability):多个Activity可以重用一个Fragment。
  3. 可适配(Adaptability):根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好。

注意:某些 Android Jetpack 库(如 Navigation、BottomNavigationView 和 ViewPager2)经过精心设计,可与 Fragment 配合使用。

Fragment生命周期

Fragment的一般生命周期如上图所示:

  • onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
  • onCreate():Fragment被创建时调用
  • onActivityCreated():当Activity完成onCreate()时调用
  • onStart():当Fragment可见时调用。
  • onResume():当Fragment可见且可交互时调用
  • onPause():当Fragment不可交互但可见时调用。
  • onStop():当Fragment不可见时调用。
  • onDestroyView():当Fragment的UI从视图结构中移除时调用。
  • onDestroy():销毁Fragment时调用。
  • onDetach():当Fragment和Activity解除关联时调用。

Fragment生命周期会经历:运行、暂停、停止、销毁。

  • 运行状态:碎片可见时,关联活动处于运行状态,其也为运行状态
  • 暂停状态:活动进入暂停状态,相关联可见碎片就会进入暂停状态
  • 停止状态:活动进入停止状态,相关联碎片就会进入停止状态,或者通过FragmentTransaction的remove()、replace()方法将碎片从从活动中移除,但如果在事务提交之前调用addToBackStack()方法,这时的碎片也会进入到停止状态。
  • 销毁状态:当活动被销毁,相关联碎片进入销毁状态。或者调用FragmentTransaction的remove()、replace()方法将碎片从活动中移除,但在事务提交之前并没有调用addToBackStack()方法,碎片也会进入到销毁状态。
    在介绍Fragment的具体使用时,先介绍一下Fragment的几个核心类
  • Fragment:Fragment的基类,任何创建的Fragment都需要继承该类
  • FragmentManager:管理和维护Fragment。他是抽象类,具体的实现类是FragmentManagerImpl。
  • FragmentTransaction:对Fragment的添加、删除等操作都需要通过事务方式进行。他是抽象类,具体的实现类是BackStackRecord

Fragment的BUG

v4包下的fragment有BUG。

Fragment 重叠问题

Fragment的onActivityResult分发的问题

Fragment应用

保存大数据

处理屏幕旋转的问题

FragmentManager(Fragment 管理器)

注意:我们强烈建议使用 Navigation 库管理应用的导航。该框架遵循使用 Fragment、返回堆栈和 Fragment 管理器的最佳做法。

FragmentManager 类负责对应用的 Fragment 执行一些操作,如添加、移除或替换它们,以及将它们添加到返回堆栈。

如果您使用的是 Jetpack Navigation 库,则可能永远不会直接与 FragmentManager 交互,因为该库会代表您使用 FragmentManager。也就是说,任何使用 Fragment 的应用都在某种程度上使用 FragmentManager,因此了解它是什么以及它如何工作非常重要。

我们将介绍如何访问 FragmentManager、FragmentManager 与 Activity 和 Fragment 相关的角色、如何使用 FragmentManager 管理返回堆栈,以及如何为 Fragment 提供数据和依赖项。

访问 FragmentManager

在 Activity 中访问

每个 FragmentActivity 及其子类(如 AppCompatActivity)都可以通过 getSupportFragmentManager() 方法访问 FragmentManager。

在 Fragment 中访问

访问子Fragment

Fragment 也能够托管一个或多个子 Fragment。在 Fragment 内,您可以通过 getChildFragmentManager() 获取对管理 Fragment 子级的 FragmentManager 的引用。

访问宿主Fragment

要访问其宿主 FragmentManager,可以使用 getParentFragmentManager()。

使用 FragmentManager

FragmentManager 管理 Fragment 返回堆栈。在运行时,FragmentManager 可以执行添加或移除 Fragment 等返回堆栈操作来响应用户互动。每一组更改作为一个单元(称为 FragmentTransaction)一起提交。

当用户按设备上的“返回”按钮时,或者当您调用 FragmentManager.popBackStack() 时,最上面的 Fragment 事务会从堆栈中弹出。换句话说,事务是反转的。如果堆栈上没有更多 Fragment 事务,并且您没有使用子 Fragment,则返回事件会向上传递到 Activity。

当您对事务调用 addToBackStack() 时,请注意,事务可以包括任意数量的操作,如添加多个 Fragment、替换多个容器中的 Fragment,等等。弹出返回堆栈时,所有这些操作会作为一项原子化操作反转。如果您在调用 popBackStack() 之前提交了其他事务,并且您没有对事务使用 addToBackStack(),则这些操作不会反转。因此,在一个 FragmentTransaction 中,应避免让影响返回堆栈的事务与不影响返回堆栈的事务交织在一起。

执行事务

如需在布局容器中显示 Fragment,请使用 FragmentManager 创建 FragmentTransaction。在事务中,您随后可以对容器执行 add() 或 replace() 操作。

例如,一个简单的 FragmentTransaction 可能如下所示:

1
2
3
4
5
supportFragmentManager.commit {
replace<ExampleFragment>(R.id.fragment_container)
setReorderingAllowed(true)
addToBackStack("name") // name can be null
}

1
2
3
4
5
6
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, ExampleFragment.class, null)
.setReorderingAllowed(true)
.addToBackStack("name") // name can be null
.commit();

在本例中,ExampleFragment 会替换当前在由 R.id.fragment_container ID 标识的布局容器中的 Fragment(如果有)。将 Fragment 的类提供给 replace() 方法可让 FragmentManager 使用其 FragmentFactory 处理实例化。

setReorderingAllowed(true) 可优化事务中涉及的 Fragment 的状态变化,以使动画和过渡正常运行。

调用 addToBackStack() 会将事务提交到返回堆栈。用户稍后可以通过按“返回”按钮反转事务并恢复上一个 Fragment。如果您在一个事务中添加或移除了多个 Fragment,弹出返回堆栈时,所有这些操作都会撤消。在 addToBackStack() 调用中提供的可选名称使您能够使用 popBackStack() 弹回到该特定事务。

如果您在执行移除 Fragment 的事务时未调用 addToBackStack(),则提交事务时会销毁已移除的 Fragment,用户无法返回到该 Fragment。如果您在移除某个 Fragment 时调用了 addToBackStack(),则该 Fragment 只会 STOPPED,稍后当用户返回时它会 RESUMED。

查找现有 Fragment

您可以使用 findFragmentById() 获取对布局容器中当前 Fragment 的引用。从 XML 扩充时,可使用 findFragmentById() 按给定的 ID 查找 Fragment;在 FragmentTransaction 中添加时,可使用它按容器 ID 进行查找。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
supportFragmentManager.commit {
replace<ExampleFragment>(R.id.fragment_container)
setReorderingAllowed(true)
addToBackStack(null)
}
...

```java
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, ExampleFragment.class, null)
.setReorderingAllowed(true)
.addToBackStack(null)
.commit();

...

ExampleFragment fragment =
(ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);

val fragment: ExampleFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14


或者,您也可以为 Fragment 分配一个唯一的标记,并使用 findFragmentByTag() 获取引用。您可以在布局中定义的 Fragment 上使用 android:tag XML 属性来分配标记,也可以在 FragmentTransaction 中的 add() 或 replace() 操作期间分配标记。
```kotlin
supportFragmentManager.commit {
replace<ExampleFragment>(R.id.fragment_container, "tag")
setReorderingAllowed(true)
addToBackStack(null)
}

...

val fragment: ExampleFragment =
supportFragmentManager.findFragmentByTag("tag") as ExampleFragment
1
2
3
4
5
6
7
8
9
10
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, ExampleFragment.class, null, "tag")
.setReorderingAllowed(true)
.addToBackStack(null)
.commit();

...

ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");

有关子 Fragment 和同级 Fragment 的特殊注意事项

https://developer.android.google.cn/guide/fragments/fragmentmanager#considerations

在任何给定的时间,只允许一个 FragmentManager 控制 Fragment 返回堆栈。如果应用在屏幕上同时显示多个同级 Fragment,或者应用使用子 Fragment,则必须指定一个 FragmentManager 来处理应用的主要导航。

如需在 Fragment 事务内定义主要导航 Fragment,请对事务调用 setPrimaryNavigationFragment() 方法,并传入一个 Fragment 的实例,该 Fragment 的 childFragmentManager 应具有主要控制权。

将导航结构视为一系列层,其中 Activity 作为最外层,封装下面的每一层子 Fragment。每一层都必须有一个主要导航 Fragment。当发生返回事件时,最内层控制导航行为。一旦最内层再也没有可从其弹回的 Fragment 事务,控制权就会回到外面的下一层,此过程会一直重复,直至到达 Activity 为止。

请注意,当同时显示两个或更多 Fragment 时,其中只有一个可以是主要导航 Fragment。如果将某个 Fragment 设为主要导航 Fragment,会移除对先前 Fragment 的指定。在上例中,如果您将详情 fragment 设为主要导航 fragment,就会移除对主 fragment 的指定。

支持多个返回堆栈

https://developer.android.google.cn/guide/fragments/fragmentmanager#multiple-back-stacks
在某些情况下,您的应用可能需要支持多个返回堆栈。一个常见示例是,您的应用使用底部导航栏。FragmentManager 可让您通过 saveBackStack() 和 restoreBackStack() 方法支持多个返回堆栈。这两种方法使您可以通过保存一个返回堆栈并恢复另一个返回堆栈来在返回堆栈之间进行交换。

注意:或者,您也可以使用 NavigationUI 组件,该组件会自动处理对底部导航栏的多个返回堆栈支持。

saveBackStack() 的工作方式类似于使用可选 name 参数调用 popBackStack():弹出指定事务以及堆栈上在此之后的所有事务。不同之处在于 saveBackStack() 会保存弹出事务中所有 fragment 的状态。

例如,假设您之前使用 addToBackStack() 提交 FragmentTransaction,从而将 fragment 添加到返回堆栈:

1
2
3
4
5
6
7
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, ExampleFragment.class, null)
// setReorderingAllowed(true) and the optional string argument for
// addToBackStack() are both required if you want to use saveBackStack().
.setReorderingAllowed(true)
.addToBackStack("replacement")
.commit();

在这种情况下,您可以通过调用 saveState() 来保存此 fragment 事务和 ExampleFragment 的状态:

1
supportFragmentManager.saveBackStack("replacement");

注意:您只能将 saveBackStack() 用于调用 setReorderingAllowed(true) 的事务,以确保可以将事务还原为单一原子操作。

您可以使用相同的名称参数调用 restoreBackStack(),以恢复所有弹出的事务以及所有保存的 fragment 状态:

1
supportFragmentManager.restoreBackStack("replacement");

注意:除非使用 addToBackStack() 传递 fragment 事务的可选名称,否则不能使用 saveBackStack() 和 restoreBackStack()。

为 fragment 提供依赖项

https://developer.android.google.cn/guide/fragments/fragmentmanager#dependencies

使用 FragmentFactory 进行测试

https://developer.android.google.cn/guide/fragments/fragmentmanager#testing

在Fragment之间添加动画过度效果

https://developer.android.google.cn/guide/fragments/animate

保存与 Fragment 相关的状态

https://developer.android.google.cn/guide/fragments/saving-state

使用应用栏(appbar)

https://developer.android.google.cn/guide/fragments/appbar

DialogFragment

https://developer.android.google.cn/guide/fragments/dialogs

测试Fragment

https://developer.android.google.cn/guide/fragments/test

Fragment的事务,真正实现/回退栈原理

Fragment懒加载解析

问题

  1. fragment 为什么需要一个空构造?
  2. 静态fragment 在哪儿初始化?

参考

  1. 享学课堂
  2. 者文_:https://www.jianshu.com/p/a4c51309bc19 ;简书
  3. https://developer.android.google.cn/guide/fragments