个性化阅读
专注于IT技术分析

Android开发人员的fragment导航模式指南

本文概述

多年来, 我看到了Android中许多不同的导航模式实现。一些应用仅使用活动, 而其他应用则与fragment和/或自定义视图混合使用。

我最喜欢的导航模式实现之一是基于”一次活动多个fragment”的哲学, 或者简称为fragment导航模式, 其中应用程序中的每个屏幕都是全屏fragment, 所有或大部分这些fragment都包含在其中。一个活动。

这种方法不仅简化了导航的实现方式, 而且具有更好的性能, 因此提供了更好的用户体验。

在本文中, 我们将介绍Android中一些常见的导航模式实现, 然后介绍基于Fragment的导航模式, 并与其他导航模式进行比较和对比。实现此模式的演示应用程序已上传到GitHub。

活动世界

一个典型的仅使用活动的Android应用程序被组织成一个树状结构(更确切地说是一个有向图), 其中根活动由启动器启动。当你在应用程序中导航时, 操作系统会维护一个活动后退堆栈。

下图显示了一个简单的示例:

fragment图片1

活动A1是我们应用程序中的入口点(例如, 它表示启动屏幕或主菜单), 用户可以从中导航到A2或A3。当你需要在活动之间进行通信时, 可以使用startActivityForResult(), 也可以在活动之间共享可全局访问的业务逻辑对象。

当你需要添加新的活动时, 需要执行以下步骤:

  • 定义新活动
  • 在AndroidManifest.xml中注册
  • 使用另一个活动的startActivity()将其打开

当然, 此导航图是一种相当简单的方法。当你需要操纵后退堆栈或必须多次重用同一活动时, 例如当你想在某些教程屏幕中导航用户, 但实际上每个屏幕都使用与基础。

幸运的是, 我们为它提供了称为任务的工具和一些适当的向后堆栈导航的准则。

然后, 在API级别11中出现了fragment…

碎片世界

Android在Android 3.0(API级别11)中引入了fragment, 主要是为了在大屏幕(例如平板电脑)上支持更动态和灵活的UI设计。由于平板电脑的屏幕比手机的屏幕大得多, 因此有更多的空间来组合和交换UI组件。fragment允许进行此类设计, 而无需你管理视图层次结构的复杂更改。通过将活动的布局分成多个fragment, 你可以在运行时修改活动的外观, 并将这些更改保存在由活动管理的后台堆栈中。 –摘自Google的Fragments API指南。

这个新玩具允许开发人员构建多窗格UI并在其他活动中重用组件。有些开发人员喜欢这个, 而其他开发人员则不喜欢。是否使用fragment是一个流行的争论, 但是我认为每个人都会同意, fragment带来了额外的复杂性, 开发人员确实需要理解它们才能正确使用它们。

Android中的全屏碎片梦m

我开始看到越来越多的示例, 其中fragment不仅代表屏幕的一部分, 而且实际上整个屏幕都是活动中包含的fragment。我什至看到一个设计, 其中每个活动都只有一个全屏fragment, 仅此而已, 而这些活动存在的唯一原因是托管这些fragment。除了明显的设计缺陷之外, 这种方法还有另一个问题。看下面的图:

fragment图片2

A1如何与F1通信?自创建F1以来, A1井对F1拥有完全控制权。例如, A1可以在创建F1时传递捆绑, 也可以调用其公共方法。 F1如何与A1通信?嗯, 这更加复杂, 但是可以通过回调/观察者模式来解决, 其中A1订阅F1, 而F1通知A1。

但是, A1和A2如何相互通信?例如, 已经通过startActivityForResult()对此进行了介绍。

现在真正的问题来了:F1和F2如何相互通信?即使在这种情况下, 我们也可以拥有一个全球可用的业务逻辑组件, 因此可以用来传递数据。但这并不总是导致优雅的设计。如果F2需要以更直接的方式将一些数据传递给F1怎么办?好了, 使用回调模式F2可以通知A2, 然后A2完成结果, 并且该结果由A1捕获, 并通知F1。

这种方法需要大量样板代码, 并迅速成为错误, 痛苦和愤怒的源头。

如果我们可以摆脱所有活动并只保留其中一个保留其余部分, 该怎么办?

fragment导航模式

多年以来, 我开始在大多数应用程序中使用”一次活动多个fragment”模式, 但我仍在使用它。关于此方法的讨论很多, 例如在这里和这里。但是我错过的是一个具体的示例, 我可以自己查看和测试。

让我们看下图:

Framgnet图片3

现在, 我们只有一个容器活动, 并且有多个fragment, 这些fragment又具有树状结构。它们之间的导航由FragmentManager处理, 它具有其后向堆栈。

请注意, 现在我们没有startActivityForResult(), 但是我们可以实现回调/观察者模式。让我们看看这种方法的优缺点:

优点:

1.更干净, 更可维护的AndroidManifest.xml

现在我们只有一个活动, 因此不再需要在每次添加新屏幕时都更新清单。与活动不同, 我们不必声明fragment。

这似乎是一件小事, 但是对于具有50多个活动的大型应用程序来说, 这可以显着提高AndroidManifest.xml文件的可读性。

查看具有几个屏幕的示例应用程序的清单文件。清单文件仍然非常简单。

<?xml version="1.0" encoding="utf-8"?>
   package="com.exarlabs.android.fragmentnavigationdemo.ui" >
   <application android:name= ".FragmentNavigationDemoApplication"
       android:allowBackup="true"
       android:icon="@mipmap/ic_launcher"
       android:label="@string/app_name"
       android:supportsRtl="true"
       android:theme="@style/AppTheme">
       <activity
           android:name="com.exarlabs.android.fragmentnavigationdemo.ui.MainActivity"
           android:label="@string/app_name"
           android:screenOrientation="portrait"
           android:theme="@style/AppTheme.NoActionBar" >
           <intent-filter>
               <action android:name="android.intent.action.MAIN" />
               <category android:name="android.intent.category.LAUNCHER" />
           </intent-filter>
       </activity>
   </application>
</manifest>

2.集中导航管理

在我的代码示例中, 你将看到我使用了NavigationManager, 就我而言, 它被注入每个fragment中。该管理器可以用作日志记录, 后向堆栈管理等的集中场所, 因此导航行为与其余业务逻辑分离, 并且不会在不同屏幕的实现中散布开来。

假设有一种情况, 我们想要启动一个屏幕, 用户可以在该屏幕上从人员列表中选择一些项目。你还希望传递一些过滤参数, 例如年龄, 职业和性别。

如果是”活动”, 则应输入:

Intent intent = new Intent();
intent.putExtra("age", 40);
intent.putExtra("occupation", "developer");
intent.putExtra("gender", "female");
startActivityForResult(intent, 100);

然后, 你必须在下面的某个位置定义onActivityResult并处理结果。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
}

我个人对此方法的问题是, 这些参数是”引号”, 它们不是强制性的, 因此, 我必须确保在缺少额外内容时, 接收活动能够处理所有不同的情况。稍后, 当进行一些重构并且不再需要例如”年龄”额外的东西时, 那么我必须在启动此活动的代码中搜索各处, 并确保所有额外的东西都是正确的。

此外, 如果结果(人员列表)以_List _的形式而不是必须反序列化的序列化形式到达, 会不会更好?

在基于fragment的导航的情况下, 一切都更加简单。你要做的就是在NavigationManager中编写一个名为startPersonSelectorFragment()的方法, 该方法带有必要的参数和回调实现。

mNavigationManager.startPersonSelectorFragment(40, "developer", "female", new PersonSelectorFragment.OnPersonSelectedListener() {
          @Override
          public boolean onPersonsSelected(List<Person> selection) {
       [do something]
              return false;
          }
      });

或搭配RetroLambda

mNavigationManager.startPersonSelectorFragment(40, "developer", "female", selection -> [do something]);

3.屏幕之间更好的通讯方式

在活动之间, 我们只能共享可以保存原语或序列化数据的捆绑包。现在, 有了fragment, 我们可以实现一个回调模式, 例如, F1可以侦听F2传递任意对象。请查看前面的示例的回调实现, 该实现返回_List _。

4.建筑碎片比建筑活动便宜

当你使用一个有5个菜单项的抽屉时, 这一点变得很明显, 并且应该在每页上再次显示该抽屉。

在纯活动导航的情况下, 每个页面都应充气并初始化抽屉, 这当然很昂贵。

在下图中, 你可以看到几个根fragment(FR *), 它们是可以从抽屉直接访问的全屏fragment, 并且只有显示这些fragment时才可以访问抽屉。图中虚线右边的所有内容均作为任意导航方案的示例。

Framgnet图片4

由于容器活动保存了抽屉, 因此我们只有一个抽屉实例, 因此在每个应该看到抽屉的导航步骤中, 你都无需充气并重新初始化它。仍不确信所有这些工作原理如何?看一下我的示例应用程序, 该示例程序演示了抽屉的用法。

缺点

我最大的担心一直是, 如果我在项目中使用基于fragment的导航模式, 那么在将来的某个地方, 我会遇到无法预料的问题, 因为fragment, 第三方库和不同的OS版本会增加复杂性, 因此很难解决。如果我不得不重构到目前为止所做的一切怎么办?

确实, 我必须解决嵌套fragment的问题, 第三方库也使用诸如ShinobiControls, ViewPagers和FragmentStatePagerAdapters之类的fragment。

我必须承认, 获得足够的碎片经验以解决这些问题是一个相当漫长的过程。但是, 在每种情况下, 问题都不是哲学不好, 而是我对fragment的理解不够。也许如果你比我更了解fragment, 你甚至都不会遇到这些问题。

我现在唯一提到的缺点是, 由于没有成熟的库可以展示基于fragment导航的复杂应用程序的所有复杂场景, 因此我们仍然会遇到无法解决的问题。

总结

在本文中, 我们看到了在Android应用程序中实现导航的另一种方法。我们将它与使用活动的传统导航哲学进行了比较, 并且我们已经看到了使用它优于传统方法的一些很好的理由。

如果你还没有, 请查看上传到GitHub实施的演示应用程序。随意分叉或通过更好的示例做出贡献, 以更好地展示其用法。

相关:Android开发人员最常犯的10个错误

赞(0)
未经允许不得转载:srcmini » Android开发人员的fragment导航模式指南

评论 抢沙发

评论前必须登录!