FragmentManager
是负责对应用的片段执行操作的类,例如添加、移除或替换它们以及将它们添加到后退栈。
如果您使用的是 Jetpack Navigation 库,则您可能永远不会直接与 FragmentManager
交互,因为它会代表您使用 FragmentManager
。但是,任何使用片段的应用都在某种程度上使用 FragmentManager
,因此了解它是什么以及它是如何工作的非常重要。
此页面涵盖
- 如何访问
FragmentManager
。 FragmentManager
在与您的活动和片段相关的角色。- 如何使用
FragmentManager
管理后退栈。 - 如何向您的片段提供数据和依赖项。
访问 FragmentManager
您可以从活动或片段访问 FragmentManager
。
FragmentActivity
及其子类(例如 AppCompatActivity
)可以通过 getSupportFragmentManager()
方法访问 FragmentManager
。
片段可以托管一个或多个子片段。在片段内部,您可以通过 getChildFragmentManager()
获取对管理片段子级的 FragmentManager
的引用。如果您需要访问其主机 FragmentManager
,可以使用 getParentFragmentManager()
。
以下是一些示例,以了解片段、其主机以及与每个片段关联的 FragmentManager
实例之间的关系。
图 1 显示了两个示例,每个示例都具有单个活动主机。这两个示例中的主机活动都向用户显示顶级导航,作为 BottomNavigationView
,它负责用应用中的不同屏幕替换主机片段。每个屏幕都作为单独的片段实现。
示例 1 中的主机片段托管两个构成分屏的子片段。示例 2 中的主机片段托管一个构成 滑动视图 显示片段的子片段。
鉴于此设置,您可以将每个主机视为具有与其关联的 FragmentManager
,该管理器管理其子片段。这在图 2 中进行了说明,以及 supportFragmentManager
、parentFragmentManager
和 childFragmentManager
之间的属性映射。
要引用的适当 FragmentManager
属性取决于调用站点在片段层次结构中的位置以及您尝试访问哪个片段管理器。
获得对 FragmentManager
的引用后,您可以使用它来操作显示给用户的片段。
子片段
一般来说,您的应用由应用项目中一个或少量活动组成,每个活动代表一组相关的屏幕。活动可能提供一个放置顶级导航的位置,以及在片段之间限定 ViewModel
对象和其他视图状态的位置。片段表示应用中的单个目标。
如果您想同时显示多个片段,例如在分屏或仪表板上,您可以使用由目标片段及其子片段管理器管理的子片段。
子片段的其他用例如下
- 屏幕滑动,在父片段中使用
ViewPager2
来管理一系列子片段视图。 - 一组相关屏幕内的子导航。
- Jetpack Navigation 使用子片段作为单个目标。活动托管单个父
NavHostFragment
并用不同的子目标片段填充其空间,以用户在应用中导航。
使用 FragmentManager
FragmentManager
管理着片段的后退栈。在运行时,FragmentManager
可以执行后退栈操作,例如响应用户交互添加或移除片段。每组更改都会作为一个称为 FragmentTransaction
的单个单元一起提交。有关片段事务的更深入讨论,请参阅 片段事务指南。
当用户点击设备上的后退按钮,或当您调用 FragmentManager.popBackStack()
时,最顶层的片段事务将从栈中弹出。如果栈中没有更多片段事务,并且您没有使用子片段,则后退事件会冒泡到 Activity。如果您正在使用子片段,请参阅 子片段和同级片段的特殊注意事项。
当您在事务上调用 addToBackStack()
时,该事务可以包含任意数量的操作,例如在多个容器中添加多个片段或替换片段。
当后退栈弹出时,所有这些操作都会作为单个原子操作反转。但是,如果您在 popBackStack()
调用之前提交了其他事务,并且您没有为该事务使用 addToBackStack()
,则这些操作不会反转。因此,在单个 FragmentTransaction
中,避免交错影响后退栈的事务和不影响后退栈的事务。
执行事务
要在布局容器内显示片段,请使用 FragmentManager
创建 FragmentTransaction
。在事务中,您可以对容器执行 add()
或 replace()
操作。
例如,一个简单的 FragmentTransaction
可能如下所示
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("name") // Name can be null }
Java
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 标识的布局容器中的片段(如果有)。将片段的类提供给 replace()
方法允许 FragmentManager
使用其 FragmentFactory
处理实例化。有关更多信息,请参阅 向片段提供依赖项 部分。
setReorderingAllowed(true)
优化了事务中涉及的片段的状态更改,以便动画和过渡能够正常工作。有关使用动画和过渡进行导航的更多信息,请参阅 片段事务 和 使用动画在片段之间导航。
调用 addToBackStack()
将事务提交到后退栈。用户以后可以通过点击后退按钮来反转事务并恢复以前的片段。如果您在单个事务中添加或删除了多个片段,则当后退栈弹出时,所有这些操作都会被撤消。在 addToBackStack()
调用中提供的可选名称使您能够使用 popBackStack()
返回到特定事务。
如果您在执行移除片段的事务时没有调用 addToBackStack()
,则在提交事务时会销毁移除的片段,并且用户无法导航回该片段。如果您在移除片段时确实调用了 addToBackStack()
,则该片段仅 STOPPED
,并在用户导航返回时稍后 RESUMED
。在这种情况下,其视图将被销毁。有关更多信息,请参阅 片段生命周期。
查找现有片段
您可以使用 findFragmentById()
获取布局容器内当前片段的引用。使用 findFragmentById()
通过给定的 ID(从 XML 中膨胀时)或通过容器 ID(在 FragmentTransaction
中添加时)查找片段。这是一个示例
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment
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);
或者,您可以为片段分配一个唯一的标签,并使用 findFragmentByTag()
获取引用。您可以使用片段的 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
Java
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");
子片段和同级片段的特殊注意事项
在任何给定时间,只有一个 FragmentManager
可以控制片段的后退栈。如果您的应用同时在屏幕上显示多个同级片段,或者如果您的应用使用子片段,则一个 FragmentManager
被指定来处理您的应用的主要导航。
要在片段事务中定义主要导航片段,请在事务上调用 setPrimaryNavigationFragment()
方法,并将具有主要控制权的 childFragmentManager
的片段实例传递进去。
将导航结构视为一系列层,其中 Activity 作为最外层,在其下方包装每一层子片段。每一层都有一个主要导航片段。
当发生后退事件时,最内层控制导航行为。一旦最内层没有更多可以从中弹出的片段事务,控制权就会返回到下一层,这个过程会一直重复,直到到达 Activity。
当同时显示两个或多个片段时,其中只有一个是主要导航片段。将片段设置为主要导航片段会取消先前片段的指定。使用前面的示例,如果您将详细信息片段设置为主要导航片段,则主片段的指定将被删除。
支持多个后退栈
在某些情况下,您的应用可能需要支持多个后退栈。一个常见的示例是,如果您的应用使用底部导航栏。FragmentManager
允许您使用 saveBackStack()
和 restoreBackStack()
方法支持多个后退栈。这些方法允许您通过保存一个后退栈并恢复另一个后退栈来在后退栈之间切换。
saveBackStack()
的工作原理类似于使用可选 name
参数调用 popBackStack()
:指定的事务以及栈中之后的所有事务都将弹出。不同之处在于 saveBackStack()
保存 弹出事务中所有片段的状态。
例如,假设您之前使用 addToBackStack()
提交 FragmentTransaction
将片段添加到后退栈中,如下例所示
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("replacement") }
Java
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();
在这种情况下,您可以通过调用 saveBackStack()
保存此片段事务以及 ExampleFragment
的状态
Kotlin
supportFragmentManager.saveBackStack("replacement")
Java
supportFragmentManager.saveBackStack("replacement");
您可以使用相同的名称参数调用 restoreBackStack()
以恢复所有弹出的事务和所有保存的片段状态
Kotlin
supportFragmentManager.restoreBackStack("replacement")
Java
supportFragmentManager.restoreBackStack("replacement");
向片段提供依赖项
添加片段时,您可以手动实例化片段并将其添加到 FragmentTransaction
中。
Kotlin
fragmentManager.commit { // Instantiate a new instance before adding val myFragment = ExampleFragment() add(R.id.fragment_view_container, myFragment) setReorderingAllowed(true) }
Java
// Instantiate a new instance before adding ExampleFragment myFragment = new ExampleFragment(); fragmentManager.beginTransaction() .add(R.id.fragment_view_container, myFragment) .setReorderingAllowed(true) .commit();
提交片段事务时,您创建的片段实例就是使用的实例。但是,在 配置更改 期间,您的 Activity 及其所有片段都将被销毁,然后使用最适用的 Android 资源 重新创建。FragmentManager
为您处理所有这些操作:它重新创建片段的实例,将它们附加到主机,并重新创建后退栈状态。
默认情况下,FragmentManager
使用框架提供的 FragmentFactory
来实例化片段的新实例。此默认工厂使用反射来查找并调用片段的无参数构造函数。这意味着您无法使用此默认工厂向片段提供依赖项。这也意味着您第一次创建片段时使用的任何自定义构造函数在默认情况下不会在重新创建期间使用。
要向片段提供依赖项或使用任何自定义构造函数,请改为创建一个自定义的 FragmentFactory
子类,然后覆盖 FragmentFactory.instantiate
。然后,您可以用自定义工厂覆盖 FragmentManager
的默认工厂,然后使用该工厂来实例化您的片段。
假设您有一个名为 DessertsFragment
的片段,它负责显示您家乡的热门甜点,并且该 DessertsFragment
依赖于一个名为 DessertsRepository
的类,该类为其提供显示正确用户界面所需的信息。
您可以定义您的 DessertsFragment
,使其在构造函数中需要一个 DessertsRepository
实例。
Kotlin
class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() { ... }
Java
public class DessertsFragment extends Fragment { private DessertsRepository dessertsRepository; public DessertsFragment(DessertsRepository dessertsRepository) { super(); this.dessertsRepository = dessertsRepository; } // Getter omitted. ... }
您 FragmentFactory
的一个简单实现可能类似于以下内容。
Kotlin
class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when (loadFragmentClass(classLoader, className)) { DessertsFragment::class.java -> DessertsFragment(repository) else -> super.instantiate(classLoader, className) } }
Java
public class MyFragmentFactory extends FragmentFactory { private DessertsRepository repository; public MyFragmentFactory(DessertsRepository repository) { super(); this.repository = repository; } @NonNull @Override public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className); if (fragmentClass == DessertsFragment.class) { return new DessertsFragment(repository); } else { return super.instantiate(classLoader, className); } } }
此示例是 FragmentFactory
的子类,它覆盖了 instantiate()
方法,以便为 DessertsFragment
提供自定义的片段创建逻辑。其他片段类由 FragmentFactory
的默认行为通过 super.instantiate()
处理。
然后,您可以将 MyFragmentFactory
指定为在构建应用程序的片段时使用的工厂,方法是在 FragmentManager
上设置一个属性。您必须在活动 super.onCreate()
之前设置此属性,以确保在重新创建片段时使用 MyFragmentFactory
。
Kotlin
class MealActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance()) super.onCreate(savedInstanceState) } }
Java
public class MealActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { DessertsRepository repository = DessertsRepository.getInstance(); getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository)); super.onCreate(savedInstanceState); } }
在活动中设置 FragmentFactory
会覆盖整个活动片段层次结构中的片段创建。换句话说,您添加的任何子片段的 childFragmentManager
都使用此处设置的自定义片段工厂,除非在较低级别被覆盖。
使用 FragmentFactory 进行测试
在单一活动架构中,使用 FragmentScenario
类独立测试您的片段。由于您无法依赖活动的自定义 onCreate
逻辑,因此您可以改为将 FragmentFactory
作为参数传递到片段测试中,如下例所示
// Inside your test val dessertRepository = mock(DessertsRepository::class.java) launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment { // Test Fragment logic }
有关此测试过程的详细信息以及完整示例,请参阅 测试您的片段。