使用标签管理 Android Fragment

avatar
HR @Tubi

图片

为了给大屏幕(如平板电脑)上更加动态和灵活的 UI 设计提供支持,Android 在3.0(API Level 11)中引入了 fragment。随着 fragment 的广泛使用,当它的使用场景和 Android 框架默认行为不一致时,管理任务栈并不简单(medium.com/square-corn… Tubi 使用 fragment 时遇到了任务栈管理的一些问题,为此,我们创建了一个开源项目 FragmentOperator (github.com/Tubitv/Frag… Kotlin 代码演示,说明如何使用 FragmentOperator 让 Android 开发者更为轻松地管理 fragment 返回栈。

从****“tag”****说起

Google 推荐使用 FragmentTransaction 进行 fragment 相关操作,这些操作被加入到返回栈中,之后我们可以做逆操作。为了较好地实现自定义的退栈操作,我们需要对一些 fragment 进行识别和分组,这样就能自定义它们的顺序。通过添加标签,我们可以简单有效地实现这一目标。

图片

以上代码与 Google 官方开发者文档 FragmentTransaction 使用方法(developer.android.com/guide/compo…

  • 给 fragment 实例添加 FragmentTag,这样我们就能在需要时使用 findFragmentByTag 取到这个实例:

    fragmentTransaction.replace(containerId, fragment, fragmentTag)

  • 当 transaction 添加到返回栈的时候也做标记,方便使用 popBackStack 将 transaction 出栈:

    fragmentTransaction.addToBackStack(fragmentTag)

在 FragmentOperator 代码库里,为了便于查找和引用,我们对 fragment 和 transaction 使用相同的 tag。每个 fragment 实例对应唯一的 tag,这样可以管理同一个 fragment 类的多个实例。并且在代码里保存和管理了每一个 fragment tag。

图片

以上代码是项目中所有 fragment 父类 BaseFragment 的一部分。由于Activity 配置更改后(例如手机方向旋转)可以重新创建 fragment 实例,新旧 fragment hashcode 不同,所以我们只创建一次 fragmentTag 并将其保存在 bundle 中。使用 getFragmentTag() 获取 transaction 对应的每个 fragment tag。

出栈自动跳过 f__ragment

FragmentOperator 的适用场景可以通过以下示例说明:

假如我们在开发一个支持游客模式的电商类应用,用户安装 App 后,在未登录状态下进入主页(HomeFragment)浏览商品缩略图,点击某个商品后,会显示一个认证页面(AuthFragment)让用户登录或者注册,登录成功后再展示商品详情页(DetailFragment),这时 fragment 的任务栈是这样的:

图片

当用户在详情页(DetailFragmemt)点击后退按钮时,默认情况下 Android FragmentManager 的 popBackStack() 方法会被调用,展示上一个页面也就是认证页面(AuthFragment),这显然不是用户想要的结果。既然用户已经登录过了,我们希望应用能跳过认证页(AuthFragment),平滑地显示主页(HomeFragment)。

图片

实现这种效果的一种方式是调用 popBackStack() 两次,但是视觉上会有一个中间页面的切换。现在的需求是只需要跳过一个页面(AuthFragment),如果遇到需要跳过多个页面的情况怎么处理?我们当然不希望多次调用 popBackStack() 方法,如果能自动处理最好不过。

Android FragmentManager 给开发者提供了一种 fragment 出栈时,跳过某些 fragment 的方法:

popBackStack(String name, int flags)

如果将 AuthFragment 标记为可跳过的 fragment 实例,并保持之前 HomeFragment 实例的 tag , 就可以跳过在 fragment 返回栈中保存的 AuthFragment,直接返回 HomeFragment。关键在于为 fragment实例保存和维护正确的 tag。

在 FragmentOperator 中,我们在 BaseFragment 中维护的一个变量 previousFragmentTag,代表当前 fragment 出栈后应该显示的 fragment;另一个变量 skipOnPop,代表当前 fragment 在返回栈出栈时是否被跳过。

图片

当加载一个新的 fragment 实例时,当前 fragment skipOnPop 值,决定了新的fragment previousFragmentTag 值,previousFragmentTag 表示新的 fragment 从返回栈中出栈时要显示的 fragment tag。

图片

当 popBackStack 被调用,通过当前 fragment previousFragmentTag 值,能获取到返回时该显示的 fragment,这样就能轻松地实现自定义 fragment 出栈顺序,跳过没必要展示的页面。

有些情况下,我们希望同一个 fragment 只有唯一一个实例出现在 fragment 任务栈中。回到刚才的例子,很有可能认证页面(AuthFragment)不只是一个页面,而是两个页面,例如登录页(SignInFragment)和注册页(SignUpFragment),登录页有按钮跳往注册页,反之亦然。

假如用户在注册页打开了登录页,又在该页面点击注册按钮重新打开注册页,如果不做特殊处理,另一个注册页的实例会被创建并加载到任务栈中,现在的任务栈看起来是这样的:

图片

某些情况下,不排除用户持续点击登录和注册按钮的可能性,这样用户点击后退按钮会遇到一个深度嵌套的导航视图,遇到这情况,开发者应该展示之前的登录或者注册页面,而不是持续创建新的页面加载到任务栈中。针对这种情况,FragmentOperator 会标记这种希望在任务栈中只保存一个实例的 fragment:

@SingleInstanceFragment

public class SignUpFragment {}

加载一个新的 fragment 时,FragmentOperator 会检查该 fragment 类有没有被添加 @SingleInstanceFragment标记,如果有,就会通过出栈退回到该 fragment 实例的前一个 fragment,然后创建新的实例添加到返回栈中,这种方式能有效地处理 fragment 内容更新情况。

图片

现在,开发者只需要将 @SingleInstanceFragment 添加给对应的 fragment,FragmentOperator库会自动处理在返回栈中只有一个实例的情况。

FragmentOperator

以上介绍的与 fragment 相关功能被整理和收录在 Tubi 的开源项目 FragmentOperator 中,希望它能让你在使用 fragment 时更加得心应手。欢迎广大开发者踊跃尝试,我们真诚地希望获取更多宝贵反馈。

本文出自 Tubi Android Team

人人都可免费观看高质量内容

一键关注了解更多

图片