7.1 UI 设计的灵活性需求
- 复杂的用户界面呈现需求
- 平板以及大尺寸手机显示问题
- 滑动屏幕等交互问题
- acticity 视图局限性
- 我们需要 activity 界面可以在运行时组装甚至时重新组装,但是其本身并不具备这样的灵活性
- activity 还得和特定的用户界面紧紧绑定
7.2 引入 fragment
- fragment 是一种控制器对象
- activity 可以委托它执行任务
- 这些任务通常是管理用户界面
- 受管的用户界面可以时一整屏或时整屏的一部分
- 采用 fragmnet 来管理 UI,可以绕开 android 系统 activity 使用规划的限制
- activity 可以委托它执行任务
- 管理用户界面的 fragment 又称为 UI fragment
- 它自己也有产生于布局文件的视图
- fragment 视图包含了用户可以交互的可视化 UI 元素
- activity 视图能预留位置供 fragment 视图插入
- 多个 fragment 则需要预留多个位置以供插入
- activity 视图和 fragment 的关系和切换如下

- fragment 可以带来灵活多变的布局,代价就是复杂的应用、更多的组件以及大量实现的代码
7.3 着手开发示例应用
CriminalIntent 就是本示例应用的名称咯。
我们先来梳理一下开发流程。
我们先来看一下整个 CriminalIntent 项目的对象图解,以便我们更好地理解开发流程

解析:
CrimeFragment的作用与activity在GeoQuiz应用中的作用差不多,都是负责创建并管理用户界面,以及与模型对象进行交互Crime实例代表某种办公室陋习crime有一个标题、一个标志 ID,一个日期和一个布尔值- 布尔值用来表示陋习是否被解决
- 简单起见,本章使用一个
Crime实例,并将其存放在CrimeFragment类的成员变量mCrime中
CrimeActivity视图(其对应的.xml文件)由FragmentLayout组件组成,FragmentLayout组件为CrimeFragment视图安排了显示位置CrimeFragment视图由一个LineaLayout组件及其三个子视图组成;CrimeFragment类中由存储它们的成员变量,并设有监听器,会响应用户操作,更新模型数据EditTextButtonCheckBox
CrimeFragment
- 首先设计一个名为
CrimeFragment的 UI fragment 来管理用户界面 - 再设计一个名为
CrimeActivity的 activity 来托管CrimeFragment实例- activity 在其视图层内提供一处位置,用来放置
fragment视图 fragment视图本身没有在屏幕上显示视图的能力;只有将它放置在 activiti 视图层级结构中,fragment视图才能显示在屏幕上
- activity 在其视图层内提供一处位置,用来放置

两类 fragment
- 原生版本的
fragment- 内置于设备系统中,如果应用要支持各个系统版本,在不同设备上运行的
fragment可能会有不同的表现(因为各个版本的维护有差异)
- 内置于设备系统中,如果应用要支持各个系统版本,在不同设备上运行的
- 支持库里的
fragment- 发布时,内置于应用中;使用支持库的
fragment在不同设备上都会由相同的表现 - 我们使用的支持库版本来自
AppCompat库
- 发布时,内置于应用中;使用支持库的
一般选用支持库中的 fragment 实现,因为考虑到 fragment API 不断引入新特性以及支持库不断更新的现状。
在 Android Studio 中增加依赖关系
要使用 AppCompat 库,项目必须加入依赖关系:
- 打开应用模块的
build.gradle文件app/build.gradle
1 | dependencies { |
Android Studio 在 build.gradle中将原来的 compile改为了 api和implementation。
参看:Why Android change ‘compile’ task to ‘implementation’ task in gradle build?
接着我们先创建模型层的 Crime 类。
创建 Crime 类
1 | public class Crime { |
解析:
UUID类是 Android 框架里的 Java 工具类- 在构造方法里,调用
UUID.randomUUID()会产生一个随机唯一 ID 值
- 在构造方法里,调用
- 使用默认的
Date构造方法初始化DAte变量- 作为
crime的默认发生时间,设置mDate变量值为当前日期
- 作为
7.4 托管 UI fragment
为了托管 UI fragment,activity 必须:
- 在其布局中为
fragment的视图安排位置 - 管理
fragment实例的生命周期
fragment的生命周期
下图展示了 fragment 的生命周期:

fragment的生命周期类似于activity的生命周期,它具有停止、暂停以及运行状态,也拥有覆盖方法,用来在一些关键节点完成一些任务fragment生命周期和activity的方法的对应关系- 因为
fragment代表activity工作,所以它的状态应该反映activity的状态
- 因为
fragment生命周期与activity生命周期的一个关键不同在于:fragment的生命周期方法由托管activity而不是操作系统调用的- 操作系统不关心
activity用来管理视图的fragment;易言之,fragment的使用是activity内部的事情
托管的两种方式
activity 托管 UI fragment 有如下两种方式:
- 在
activity布局中添加fragment- 使用布局
fragment - 简单但不灵活:在
activity布局中添加fragment,就等同于将fragment及其视图与activity的视图绑定在一起,并且在activity的生命周期过程中,无法替换fragment视图
- 使用布局
- 在
activity代码中添加fragment- 比较复杂,但是也是唯一可以动态控制
fragment的方式 - 何时添加
fragment以及随后可以完成何种具体任务由你自己决定;也可以移除、替换和重新添加当前fragment等等
- 比较复杂,但是也是唯一可以动态控制
为了追求真正灵活的 UI 设计,就必须通过代码的方式添加fragment。
接下来我们将定义 CrimeActivity 的布局。
定义容器布局
尽管我们选择的是在 activity 代码中添加 UI fragment,但是我们依旧 要在 activity 视图层级结构中为 fragment 视图安排位置。
在 CrimeActivity 的布局中,该位置就是下图所示的FrameLayout:

FragmentLayout是服务于CrimeFragment的容器视图- 此容器视图是个通用性视图,不单用于
CrimeFragment类,你还可以用它托管其他的fragment
- 此容器视图是个通用性视图,不单用于
我们会在 activity_crime.xml 文件中使用 FragmentLayout 作为默认布局:
1 | <?xml version="1.0" encoding="utf-8"?> |
- 当前的
activity_crime.xml布局文件仅由一个服务于单个fragment的容器视图组成- 除了自身组件之外,托管
activity布局还可定义多个容器视图
- 除了自身组件之外,托管
7.5 创建 UI fragment
创建 UI fragment 的步骤与创建 activity 的步骤相同:
- 定义用户界面布局文件
- 创建
fragment类并设置其视图为定义的布局 - 编写代码以实例化组件
定义 CrimeFragment 的布局
CrimeFragment视图用来显示包含在Crime类实例中的信息
balabala…
创建 CrimeFragment 类
- 实现
fragment生命周期方法
CrimeFragment类是与模型及视图对象交互的控制器,用于显示特定的 cirme 的明确信息。并在用户修改这些信息立即进行更新。
我们上一个例子中,activity通过其生命周期方法完成了大部分逻辑控制工作。在本个例子中,这些工作 fragment 的生命周期方法完成的。
CrimeFragment.java
1 | import android.support.v4.app.Fragment; |
解析:
Fragment.onCreate(Bundle)是公共方法,而Activity.onCreate(Bundle)是受保护方法Fragment.onCreate(Bundle)方法及其他Fragment生命周期方法必须是公共方法,因为托管fragment的activity要调用它们
Fragment同样具有保存及获取状态的bundle- 类似于使用
Activity.onSaveInstanceState(Bundle),我们需要覆盖Fragment.onSaveInstanceState(Bundle)来使用
- 类似于使用
fragment的视图不是在Fragment.onCreate(Bundle)中生成的,虽然我们在该方法中配置了fragment实例,但是创建和配置fragment视图是在另一个fragment生命周期方法完成的
1 | public View onCrateView(LayoutInflater inflater, ViewGroup container, |
- 该方法实例化
fragment视图的布局,然后将实例化的View返回给托管的activityLayoutInflater, ViewGroup是必要参数,Bundle用来存储恢复数据,可供该方法从保存状态下重建视图
下面我们在 CrimeFragment.java 中,添加 onCreateView 方法的实现代码,从 fragment_crime.xml 布局中实例化返回布局。
1 |
|
解析:
- 在
onCreateView(...)方法中,fragment的视图是直接通过调用LayoutInflater.inflate(...)方法并传入布局的资源 ID 生成的- 第二个参数是视图的父视图,我们通常需要父视图来正确配置组件
- 第三个参数告诉布局生成器是否将生成的视图添加个给父视图
- 传入
flase表示我们将以代码的方式添加视图
- 传入
- 在
fragment中实例化组件
fragment中的 EditText, CheckBox, Button 组件,也都是在 onCreateView(...) 方法里实例化的。
1 |
|
- 对比
Activity中实例化组件,Fragment中需要手动调用View.findViewById()方法 onTextChanged(..)方法中,调用CharSequence(表示用户输入) 的toString()方法- 该方法最后返回用来设置的
Crime标题字符串
- 该方法最后返回用来设置的
接下来设置 Button 组件,让他显示 crime 的发生日期。
1 | ... |
此处只是显示日期,而点击功能没有启用。
接着设置 ChcekBox 组件。引用它并设置监听器,根据用户操作,更新 mSolved 状态。
1 | ... |
7.6 向 FragmentManager 添加 UI fragment
fragment 自己无法在屏幕上显示视图,我们需要把 CrimeFragment 添加给CrimeActivity。
FragmentManager类负责管理fragment并将它们的视图添加到activity的视图层级结构中Activity类中添加了FragmentManager

FragmentManager具体管理fragment队列fragment事务回退栈
在本例中,我们只需关心 FragmentManager 管理的 fragment 队列。
- 以代码的方式将
fragment添加给activity,需要直接调用activity的fragmentManager- 先获取
fragmentManager本身 - 在
CrimeActivity.java中,在onCreate(Bundle)方法中添加代码取得fragmentManager
- 先获取
获取fragmentManager(CrimeActivity.java)
1 | public class CrimeActivity extends AppCompatActivity { |
fragment事务
获取 fragmentManager 后,再获取一个 fragment 交给它管理。
添加一个CrimeFragment(CrimeActivity.java)
1 | Fragment fragment = fm.findFragmentById(R.id.fragment_container); |
new–>add–>commit- 事务的创建到提交的过程
fragment事务用来被添加、移除、附加、分离或替换fragment队列中的fragment- 这是
fragment动态组装和重新组装用户界面的关键
- 这是
Fragment.beginTransaction()- 创建并返回
fragmentTransaction实例- 该实例类支持流接口(fluent interface)的链式方法调用,以此配置
FragmentTransaction再返回它
- 该实例类支持流接口(fluent interface)的链式方法调用,以此配置
- 创建并返回
add是整个事务的核心- 参数
- 容器视图资源 ID
- 告诉
FragmentManager,fragment视图应该出现在activity视图的什么位置 - 作为
FragmentManager队列中fragment的唯一标志
- 告诉
- 新创建的
CrimeFragment
- 容器视图资源 ID
- 参数
- 从
FragmentManager中获取CrimeFragment,使用容器视图资源 ID 就行了- 如果要向
activity添加多个fragment,通常就需要分别为每个fragment创建具有不同 ID 的不同容器
- 如果要向
总结起来,其是就是:

FragmentManafer和 fragment 生命周期

activity的FragmentManager负责调用队列中的fragment的生命周期方法- 添加
fragment供FragmentManager管理时,onAttach(Context), onCreate(Bundle)和onCreateView(...)方法会被调用 - 托管
activity的onCreate(Bundle)方法执行后,onActivityCreated(Bundle)方法也不会被调用- 因为
CrimeActivity.onCreate(Bundle)方法中添加CrimeFragment,所以fragment被添加后,该方法会被调用
- 因为
- 添加
- 当
activity处于运行状态时,添加fragment后FragmentManager会立即驱赶(指让fragment走得快一点…)fragment,调用一系列必要的生命周期方法,快速赶上activity的步伐- 一旦赶上,托管的
activity的FragmentManager就会边接收操作系统的指令,边调用其他生命周期方法,让fragment与activity的状态保持一致
7.7. 采用 fragment 应用架构
尽管 fragment 组件可以复用,但是正确使用 fragment 非常重要 ,否则就边成了滥用。
fragment时用来封装关键组件以便复用- 关键组件:针对应用的整个屏幕来讲的
- 单屏使用大量的
fragment,不仅使代码充斥fragment事务处理,模块的职责分工也会不清晰 - 如果由很多零碎的晓组件需要复用,比较好的架构设计时使用定制视图
- 实践证明:应用单屏最多使用 2 ~ 3 个
fragment

使用 fragment 的理由
- 实际开发中,尽管有时候可用可不用,但是我们还是会采用
fragment- 因为后期添加
fragment是一个大坑
- 因为后期添加
作者坚信的 AUF(Always Use Fragments),总是使用fragment。
7.8 深入学习:fragment与支持库
AppCompat库没有实现fragment功能,它依赖于support-4库,是个后者实现了fragment功能support-v4实现了fragment功能- 其库内也有一个
Activity子类:FragmentActivity - 而
AppCompatActivity时FragmentActivity的子类,所以应用能使用支持库版本的fragment
- 其库内也有一个

7.9 深入学习:为什么优先使用支持库版本的 fragment
支持库版本的 fragment 使用起来最方便
- Google 每年会多次更新支持库,并借此引入新特性、修复 bug
- 支持库的本意是方便在不支持该 API 的旧版本上使用
- 支持库版本的
fragment没有显著的缺点- 功能实现上和系统内置的没有不同
- 唯一缺点是导入支持库包会占用额外空间
如何使用内置版?
如果要使用内置版本的fragment,需要对项目作如下改动:
- 弃用
FragmentActivity类,改用标准库中的Activity类(android.app.Activity) - 弃用
android.support.v4.app.Fragment类,改用android.app.Fragment类 - 弃用
getSupportFragmentManager()方法,改用getFragmentManager()方法获取FragmentManager