Android官方文档学习笔记
Fragment管理器
注意:我们强烈建议使用导航库管理应用的导航。该框架遵循使用 Fragment、返回和 Fragment 管理器的最佳实践。
FragmentManager 类负责对应用程序的 Fragment 执行一些操作,如添加、移除或替换它们,以及将它们添加到结果返回。
如果您使用的是Jetpack的导航库,可能则永远不会直接与FragmentManager交互,因为该库会代表您使用FragmentManager。也就是说,任何使用片段的应用都在某种程度上使用FragmentManager,因此了解它是什么以及它如何工作非常重要。
本主题介绍如何访问FragmentManager、FragmentManager与 Activity 和 Fragment 相关的角色、如何使用FragmentManager管理返回视图,以及如何为 Fragment 提供数据和依赖项。
访问 FragmentManager
在活动中访问
每个FragmentActivity及其子类(如AppCompatActivity)都可以通过getSupportFragmentManager()方法访问FragmentManager。
在 Fragment 中访问
Fragment 也能够实现一个或多个 Fragment。在 Fragment 内,您可以通过getChildFragmentManager()获取对管理 Fragment 子级FragmentManager的引用。如果您需要访问其能力FragmentManager,可以使用getParentFragmentManager()。
下面我们来看看几个示例,Fragment、其半径以及与Fragment关联的FragmentManager实例之间的关系。
图1.两个界面图形示例,展示了片段关系和活动之间的关系。
图1显示了两个示例,每个示例中都有一个活动宿主。这两个示例中的宿主活动以都BottomNavigationView的形式向用户显示顶级导航,该视图负责以应用中的不同屏幕换出宿主片段每个屏幕都实现为单独的 Fragment。
示例 1中的片段 Fragment 一个两个子片段,这些片段构成分离视图屏幕。示例 2中的内容显示 Fragment 一个子 Fragment Fragment,构成一个视图的 Fragment。
基于此设置,您可以将每个宿主视为具有与其关联的FragmentManager,用于管理其子片段。图2说明了这一点,并显示了supportFragmentManager,parentFragmentManager状语从句:childFragmentManager之间的属性映射。
需要引用相应的FragmentManager属性调用调用点在 Fragment 层次结构中的位置,以及您的实验访问 Fragment 管理器。
对FragmentManager进行引用后,您就可以使用它来拒绝向用户显示的 Fragment。
子片段
粒子,应用程序应用项目中的一个或多个活动组成,其中每个活动表示可能会出现一组相关的屏幕。活动提供一个点来环境导航,并提供一个位置来限定ViewModels以及片段之间的其他视图状态的范围。应用程序中的每个目标应用程序由一个 Fragment 表示。
如果您想要一次显示多个 Fragment(如在拆分视图中或信息中心内),应使用子 Fragment,它们由各个 Fragment 及其子 Fragment 管理器进行管理。
子片段的其他例子可能包括:
- 快照,其中的父片段 Fragment 中的ViewPager2管理细腻子 Fragment 视图。
- 一组相关屏幕中的子导航。
- Jetpack Navigation 的子碎片将成为一个新NavHostFragment的工具。
使用 FragmentManager
FragmentManager管理 Fragment 返回。在运行时,FragmentManager可以执行添加或 Fragment 等返回操作触发来响应用户交互。每一组更改作为一个单元(以下FragmentTransaction)一起提交。
当用户按设备上的“返回”按钮时,或者当您调用FragmentManager.popBackStack()时,最上面的 Fragment 事务会从中弹出。换句话说,事务是反转的。如果堆栈上没有更多片段事务,并且您没有使用子片段,则返回事件会向上传递到活性的影响。如果您使用子片段,参阅请有关子片段和同级片段的特殊注意事项。
当您对事务调用addToBackStack()时,请注意,事件可以包括任意数量的操作,如添加多个片段、替换多个容器中的片段,弹出返回场景时,所有这些操作会作为原子原子化操作。如果可以。如果在调用popBackStack()之前提交了其他事务,并且您没有对事务addToBackStack(),则这些操作不会让您使用。因此,在一个FragmentTransaction中,应避免让影响返回结果的事务与返回的事务。
执行事务
请在布局组件中显示片段,使用FragmentManager创建FragmentTransaction。在事务中,您可以对容器执行add()或replace()操作。
例如,一个简单的FragmentTransaction可能如下所示:
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_containerID 标识的组件容器中的 Fragment(如果有)提供给replace()方法可让其FragmentManager使用FragmentFactory处理实例化。
setReorderingAllowed(true)可优化事务中涉及到 Fragment 的状态变化,公园动画和更新的正常进行。
调用addToBackStack()聚合服务到返回用户。用户通过点击“返回按钮”可以成功并恢复上一个片段。如果您在一个事务中添加或移除了多个 Fragment,就会弹出返回时,所有这些操作全部撤消。在addToBackStack()调用中提供选择的名称,使您能够popBackStack()弹回到特定的事务。
如果您在删除 Fragment 的事务时未调用addToBackStack(),则提交事务时会删除的 Fragment,用户无法返回到该 Fragment。如果您在删除某个 Fragment 时调用了addToBackStack(),则该 Fragment 找到STOPPED,稍等当用户返回时它会RESUMED。注意,在这种情况下,其视图会被慎重。
现有的Fragment
您可以使用findFragmentById()获取对组件容器中当前 Fragment 的引用。从 XML 扩展定时,可使用findFragmentById()按给的 ID 查看 Fragment;在FragmentTransaction中添加时,可使用它按容器 ID 进行查找。示例如下:
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);
或者,您也可以为 Fragment 分配一个唯一的标记,并使用findFragmentByTag()获取引用。您可以在布局中定义的 Fragment 上使用android:tagXML 属性来分配标记,也可以在FragmentTransaction中的add()或replace()操作期间分配标记。
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应用一个控制 Fragment 返回一个。如果应用在屏幕上同时显示多个同级 Fragment,或者使用子 Fragment,则指定一个FragmentManager来处理应用的主要导航。
约会在 Fragment 事务内定义主要导航 Fragment,请对事务调用setPrimaryNavigationFragment()方法,并复制一个 Fragment 的实例,该 Fragment 的childFragmentManager应具有主要控制权。
将导航点击一层,其中的活动是最外层,包含下面的每一层Fragment。每一层都必须有一个主要导航Fragment。当发生返回事件时,最内层控制导航行为。最内层再也没有可从其弹回的碎片事务,控制权就会回到外面的下一层,此过程会一直重复,直到到达活动为止。
请注意,当同时显示两个或更多 Fragment 时,其中只有一个可以是主要导航 Fragment。如果将某个 Fragment 设为主要导航 Fragment,则会移除对上一个 Fragment 的指定。将详情片段设为主要导航片段,将移除对主片段的指定。
支持一次返回结果
在下面的情况下,一个常见的示例是,您的应用程序使用底部导航栏。FragmentManager可让您通过saveBackStack()和restoreBackStack()方法支持一些返回结果。这两种方法使您可以通过一个返回结果并恢复一个结果来在返回结果之间进行交换。
注意:或者,您也可以使用NavigationUI组件,该组件会自动处理对底部导航栏的多个返回结果支持。
saveBackStack()工作的方式类似于使用可选name参数调用popBackStack():指定弹出事务以及堆栈上在此之后的所有事务不同之处在于saveBackStack() 会保存弹出事务中所有片段的状态。
假设,您之前使用addToBackStack()提交FragmentTransaction,从而将片段添加到返回示例:
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()来保存此片段事务和ExampleFragment的状态:
supportFragmentManager.saveBackStack("replacement");
注意:您只能saveBackStack()用于调用setReorderingAllowed(true)的事务,可以将事务还原为单个原子操作。
您可以使用相同的名称参数调用restoreBackStack(),以恢复所有弹出的事务以及所有保存的片段状态:
supportFragmentManager.restoreBackStack("replacement");
注意:使用addToBackStack()传递片段某事的任选名称,否则不能使用saveBackStack()和restoreBackStack()。
为片段提供依赖项
添加 Fragment 时,您可以手动实例化 Fragment 将其添加到FragmentTransaction。
// Instantiate a new instance before adding
ExampleFragment myFragment = new ExampleFragment();
fragmentManager.beginTransaction()
.add(R.id.fragment_view_container, myFragment)
.setReorderingAllowed(true)
.commit();
当提交 Fragment 事务时您创建的 Fragment 实例就是使用的实例。不过,在配置更改期间,活动及其所有 Fragment 都被敏感,然后使用最适用的Android 资源创建。FragmentManager会为您处理所有这些重新操作。它会很重的创建 Fragment 的实例,将其附加到神奇,并清晰的创建返回状态。
默认情况下,FragmentManager使用框架提供的FragmentFactory实例化 Fragment 的新实例。此默认工厂使用反射来打印和调用 Fragment 的无参数构造函数。这表示,您无法使用此默认工厂为 Fragment 提供依赖项。这也意味着着着,默认情况下,在重新创建过程中,不会使用你首次创建的 Fragment 时所用的任何自定义构造函数。
为 Fragment 提供依赖项或使用任何自定义构造函数,您必须创建自定义FragmentFactory子类,然后FragmentFactory.instantiate替换FragmentManager。
假你有一个DessertsFragment,重点显示您的家乡流行的笑话。我们仿佛DessertsFragment依赖于DessertsRepository类,该类可提供向用户正确显示界面所需的信息。
你可以DessertsFragment定义为在其构造函数中需要DessertsRepository实例。
public class DessertsFragment extends Fragment {
private DessertsRepository dessertsRepository;
public DessertsFragment(DessertsRepository dessertsRepository) {
super();
this.dessertsRepository = dessertsRepository;
}
// Getter omitted.
...
}
FragmentFactory 的简单实现可能与以下代码类似。
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提供自定义片段创建逻辑。其他片段通过类super.instantiate()由FragmentFactory的默认行为处理。
您以后可以通过在FragmentManager上设置一个属性,将MyFragmentFactory指定为要在构造应用的 Fragment 时使用的工厂。您必须在活动super.onCreate()之前设置此属性时,才能确保在创建碎片时使用MyFragmentFactory。
public class MealActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
DessertsRepository repository = DessertsRepository.getInstance();
getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository));
super.onCreate(savedInstanceState);
}
}
在 Activity 中FragmentFactory会替换整个 Activity 的 Fragment 层次中的 Fragment 设置创建结构注意事项,您添加的任何子 Fragment 的childFragmentManager任何子Fragment都使用这里设置的自定义 Fragment 工厂,不会在较低的级别被替换。
使用FragmentFactory进行测试
在一个 Activity 架构中,您应使用FragmentScenario类在隔离的条件下测试 Fragment。由于您无法依赖于 Activity 的自定义onCreate逻辑,因此改为将其FragmentFactory作为参数 Fragment 测试,如下示例所示:
// Inside your test
val dessertRepository = mock(DessertsRepository::class.java)
launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment {
// Test Fragment logic
}