Android开发-API指南-Fragment
Fragments
英文原文:http://developer.android.com/guide/components/fragments.html
采集日期:2014-12-31
Fragment
代表 Activity 当中的一项操作或一部分用户界面。
一个 Activity 中的多个 Fragment 可以组合在一起,形成一个多部分拼接而成的用户界面组件,并可在多个 Activity 中复用。一个 Fragment 可被视为 Activity 中一个模块化的部分,
它拥有自己的生命周期,并接收自己的输入事件,在 Activity 运行过程中可以随时添加或移除它
(有点类似“子 Activity”,可在不同的 Activity 中重用)。
Fragment 必须嵌入某个 Activity 中,其生命周期直接受到宿主 Activity 生命周期的影响。 例如,当 Activity 被暂停(Paused)时,其内部所有的 Fragment 也都会暂停。 而当 Activity 被销毁时,它的 Fragment 也都会被销毁。 不过,在 Activity 运行期间( 生命周期状态处于恢复(Resumed) 状态时),每一个 Fragment 都可以被独立地操作,比如添加或移除。 在执行这些操作事务时,还可以将它们加入该 Activity 的回退栈(Back Stack)中 — Activity 回退栈的每个入口就是一条操作过的 Fragment 事务记录。 回退堆栈使得用户可以通过按下 回退(Back) 键来回退 Fragment 事务(后退一步)。
当把 Fragment 加入 Activity 布局(Layout) 后,它位于 Activity View 层次架构(Hierarchy)的某个
ViewGroup
里,且拥有自己的 View 布局定义。
通过在 Activity 的 Layout 文件中声明 <fragment>
元素,可以在 Layout 中添加一个 Fragment。
也可以用程序代码在已有的
ViewGroup
中添加一个 Fragment。
不过, Fragment 并不一定非要是 Activity 布局的一部分,它也可以没有自己的界面,而是用作 Activity 的非可视化工作组件。
本文介绍了如何创建应用程序并使用 Fragment , 包括: Fragment 在加入 Activity 的回退栈后如何维护自身的状态、 如何与 Activity 及同一 Activity 中的其他 Fragment 共享事件、 如何构建 Activity 的 Action Bar,等等。
设计理念
Android 自 3.0 (API 级别 11)开始引入 Fragment,主要用来在平板电脑之类的大屏幕设备上提供更加动态、灵活的用户界面设计。 因为平板电脑的屏幕要比手机大得多,可以有更多的空间来组合和变换 UI 组件的位置。 有了 Fragment ,就可以在设计界面时不用去维护 View 层次架构的复杂变化。 通过把 Activity 的布局拆分到多个 Fragment 中去,可以在运行时修改 Activity 的外观,还可以把这些变动保存到 Activity 的回退栈中。
例如,某个新闻应用可以在左侧用一个 Fragment 显示文章列表,在右侧用另一个 Fragment 显示文章内容, 这两个 Fragment 并排显示在同一个 Activity 中,每个 Fragment 拥有各自的生命周期回调方法,并处理自己的用户输入事件。 这样,就不是用一个 Activity 选择文章、另一个 Activity 阅读文章了,用户可以在同一个 Activity 中完成这两个操作, 图1 给出了平板电脑上的布局示意。
每个 Fragment 都应被设计为模块化、可复用的 Activity 组件。 也就是说,由于每个 Fragment 都定义了自己的布局, 并用自己的生命周期回调方法定义了自己的运行方式, 一个 Fragment 可以被放入多个 Activity 中去, 所以它应该被设计为可复用的,并避免从一个 Fragment 中直接操作另一个 Fragment。 这一点非常重要,因为模块化的 Fragment 可以实现不同屏幕尺寸下对 Fragment 组合的修改。 在设计通用于平板电脑和手机的应用程序时,可以在各种不同的布局下复用 Fragment ,以便根据可用屏幕空间的大小优化用户的体验。 例如,在手机上,如果在同一个 Activity 中放不下多个 Fragment , 就可能有必要把多个 Fragment 分开,界面上只显示单个组件。
图 1. 两个由 Fragment 定义的 UI 模块示例,在平板电脑上可以并入一个 Activity 中显示,而在手机上则可以分开显示。
例如 — 继续以上面的新闻应用为例 — 如果在平板电脑尺寸的设备上运行,此应用可以把两个 Fragment 都嵌入 Activity A 中显示。 而在手机尺寸的屏幕上,空间不足以同时容纳两个 Fragment , 所以 Activity A 就仅包含一个文章列表 Fragment , 用户选中某篇文章后,就会打开 Activity B,里面放置了阅读文章用的第二个 Fragment 。 这样,通过以不同的组合方式复用 Fragment ,应用程序就能同时支持平板电脑和手机了,如图 1 所示。
关于针对不同屏幕参数设计不同的 Fragment 组合,详情请参阅指南 同时支持平板电脑和手机 。
创建 Fragment
![](http://images0.cnblogs.com/blog/704105/201501/041339303418877.png)
图 2. Fragment 的生命周期(当其 Activity 正在运行时)
要创建 Fragment ,必须创建一个
Fragment
对象(或者它的已存在子类)的子类。
Fragment
类的代码与
Activity
非常相像。它包含了一些与 Activity 类似的回调方法,比如
onCreate()
、
onStart()
、
onPause()
和
onStop()
。
实际上,如果要把某个已有的 Android 应用转换成使用 Fragment 的应用,只要把 Activity 回调方法中的代码移入对应的 Fragment 方法中去即可。
通常,至少要实现以下生命周期方法:
onCreate()
- 系统将在创建 Fragment 时调用本方法。 在本方法的代码中,应该对那些在 Fragment 暂停或停止时需要保存状态的必要组件进行初始化。
onCreateView()
-
系统将在 Fragment 第一次绘制用户界面时调用本方法。
为了绘制 Fragment 的用户界面,必须让本方法返回一个
View
,用作 Fragment 布局的根元素。 如果 Fragment 没有用户界面,则可以返回 null 。 onPause()
- 只要用户有要离开 Fragment 的迹象(尽管这并不意味着一定会销毁 Fragment),系统将会首先调用本方法。 通常这时应该提交当前用户会话(Session)中需要保存的的所有改动(因为用户可能不会再回来了)。
大部分应用程序都至少应该为每个 Fragment 实现以上三个方法, 为了能够应对 Fragment 各个生命周期状态的变化,还应该实现更多其他的回调方法。 所有的生命周期回调方法将在处理 Fragment 的生命周期一节中进行详细讨论。
除了基类
Fragment
之外,还有其他一些子类可供扩展:
DialogFragment
-
显示一个浮动的对话框。
用这个类创建的对话框可以很好地替代
Activity
类的助手(Helper)方法创建的对话框, 因为 Fragment 对话框可以置入 Activity 的回退栈,这样用户就可以返回已经关闭的 Fragment 了。 ListFragment
-
显示一个列表框,其中的列表项由适配器(Adapter)提供(比如
SimpleCursorAdapter
,这类似于ListActivity
)。本 Fragment 提供了很多管理列表 View 的方法,比如用于处理点击事件的onListItemClick()
回调方法。 PreferenceFragment
-
以列表的方式显示一个由
Preference
对象组成的层次结构,类似于PreferenceActivity
。这在创建应用程序的“设置” Activity 时会很有用。
添加用户界面
Fragment 通常用作 Activity 用户界面的一部分,并且把自己的布局提供给 Activity 使用。
要为 Fragment 提供一个布局,必须实现
onCreateView()
回调方法,当 Fragment 需要绘制自己的布局时, Android 系统将会调用该方法。
该方法必须返回一个
View
,用作 Fragment 布局的根元素 。
注意:
如果 Fragment 是
ListFragment
的子类,
onCreateView()
的默认代码将返回一个
ListView
,这时就不必再自行实现这个 View 了。
要让
onCreateView()
返回一个布局 ,可以从
Layout 资源
中读取 XML 定义并生成。
onCreateView()
里有一个
LayoutInflater
对象,可有助于完成建立布局的工作。
下面给出了一个
Fragment
子类的例子,它从 example_fragment.xml
文件中载入布局定义:
1 public static class ExampleFragment extends Fragment { 2 @Override 3 public View onCreateView(LayoutInflater inflater, ViewGroup container, 4 Bundle savedInstanceState) { 5 // 载入 Fragment 的布局 6 return inflater.inflate(R.layout.example_fragment, container, false); 7 } 8 }
创建布局
在以上例子中, R.layout.example_fragment
引用了一个名为 example_fragment.xml
的布局资源,资源存放于应用程序的资源目录中。 关于如何创建 XML 格式的布局定义,请参阅 用户界面 文档。
传给 onCreateView()
的 container
参数是当前 Fragment 布局的上一级 ViewGroup
(来自 Activity 的布局),Fragment 的布局定义都将插入其中。 savedInstanceState
参数是一个 Bundle
对象,如果该 Fragment 是被恢复运行,这里面给出了前一次 Fragment 实例的有关数据(关于还原状态的更多信息,在 处理 Fragment 的生命周期一节中讨论)。
inflate()
方法有三个参数:
- 用来生成布局的资源 ID。
- 要生成的布局的上一级
ViewGroup
。传入container
是非常重要的,系统用它作为新生成布局的根 View ,它将被设为新布局的父 View 。 - 布尔值,指明了新生成的布局是否要与
ViewGroup
(第二个参数)绑定。 (在此例中设为 False ,因为系统已经是要把新生成的布局插入到container
中去了。 这里如果传入 True 将会在最终的布局中创建一个多余的 ViewGroup 。)
这就是创建一个自带布局的 Fragment 的过程。接下来,需要把 Fragment 加入到 Activity 中去。
把 Fragment 加入 Activity
通常,Fragment 向宿主 Activity 提供了一些用户界面,这些 UI 嵌入到 Activity 整体的 View 层次结构中,成为其中的组成部分。 把 Fragment 加入 Activity 布局的途径有两种:
- 在 Activity 布局文件中声明 Fragment
这种情况下,可以把 Fragemt 视为 View ,指定其 Layout 属性。 比如,下面是包含两个 Fragment 的 Activity 的布局文件:
< ?xml version="1.0" encoding="utf-8"? > < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" > < fragment android:name="com.example.news.ArticleListFragment" android:id="@+id/list" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" / > < fragment android:name="com.example.news.ArticleReaderFragment" android:id="@+id/viewer" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" / > < /LinearLayout >
<fragment>
的android:name
属性 指定了要在布局中实例化的Fragment
类。在创建这个 Activity 布局时,系统会实例化布局中每一个 Fragment,并调用每个 Fragment 的
onCreateView()
方法以读取各自的布局定义。 系统会在<fragment>
元素的位置直接插入 Fragment 返回的View
。注意: 每个 Fragment 的标识符都必须唯一,当重启 Activity 时,系统可以用此标识符来恢复 Fragemt (还可以用此标识符来记录 Fragment 状态,以便执行移除之类的事务操作。 给 Fragment 赋予 ID 的方式有三种:
- 为
android:id
属性给定一个唯一的 ID。 - 为
android:tag
属性给定一个唯一的字符串。 - 如果前两者都未指定,系统就采用父容器 View 的 ID。
- 为
- 或者,还可以通过程序代码在已有的
ViewGroup
内添加 Fragment。在 Activity 运行的任何时刻,都可以在 Activity 布局中添加 Fragment 。 只需要指定放置 Fragment 的
ViewGroup
即可。要想在 Activity 中执行 Fragment 事务(如添加、移除、替换 Fragment),必须使用
FragmentTransaction
提供的 API 来完成。 可以采用如下途径从Activity
获得FragmentTransaction
的一个实例:1 FragmentManager fragmentManager = getFragmentManager(); 2 FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
然后,就可以用
add()
方法添加一个 Fragment ,指定要添加的 Fragment 及其所在的 View 即可。 例如:1 ExampleFragment fragment = new ExampleFragment(); 2 fragmentTransaction.add(R.id.fragment_container, fragment); 3 fragmentTransaction.commit();
传入
add()
的第一个参数是将要放置 Fragment 的ViewGroup
,给出资源 ID 即可。 第二个参数是要添加的 Fragment。一旦用
FragmentTransaction
做出了改动,就必须调用commit()
让改动生效。
添加不带用户界面的 Fragment
上述例子演示了如何在 Activity 中添加一个提供用户界面的 Fragment 。 然而,还可以用 Fragment 为 Activity 执行一个不带界面的后台操作。
要为 Activity 添加一个不带用户界面的 Fragment,请使用 add(Fragment, String)
(这里要为 Fragment 指定唯一的字符串标签“tag”,而不是 View 的 ID )。 因为未与 Activity 布局中的任何 View 关联,所以此类 Fragment 不会接收到 onCreateView()
调用。这样就不需要实现此方法了。
为 Fragment 定义的字符串标签并不是只能用于无界面的 Fragment,带有用户界面的 Fragment 也可以给定字符串标签。 但是,如果 Fragment 没有用户界面,字符串标签就是标识它的唯一方式。 如果以后需要从 Activity 中获取该 Fragment ,就要用到 findFragmentByTag()
了。
关于使用不带界面的 Fragment 执行后台操作的 Activity 示例,请参阅例程 FragmentRetainInstance.java
管理 Fragment
要对 Activity 中的 Fragment 进行管理,需要用到 FragmentManager
。它可以通过调用 Activity 的 getFragmentManager()
方法来获取。
通过 FragmentManager
可以完成的操作包括:
- 用
findFragmentById()
方法获取 Activity 中已存在的 Fragment (适用于向 Activity 布局提供了用户界面的 Fragment ),或者用findFragmentByTag()
方法获取(不论是否提供界面都可使用)。 - 用
popBackStack()
方法将 Fragment 从回退栈中弹出(模拟由用户发起的回退Back指令)。 - 用
addOnBackStackChangedListener()
注册一个用于监视回退栈变化的侦听器。
关于这些方法的更多详情,请参阅 FragmentManager
类的文档。
在上一节的示例中,可以用 FragmentManager
启动一个 FragmentTransaction
,这样就可以执行一些诸如添加和移除之类的事务了。
执行 Fragment 事务
在 Activity 中使用 Fragment 的一大好处,就是可以根据用户的需求对其进行添加、移除、替换及其他操作。 提交给 Activity 的一组操作被称作一个事务,可以通过 FragmentTransaction
提供的 API 来执行事务。 每一个事务都可被保存到 Activity 的回退栈中,用户可以回退这些 Fragment 操作(类似于回退多个 Activity 一样)。
以下演示了从 FragmentManager
中获取一个 FragmentTransaction
的实例:
FragmentManager fragmentManager =getFragmentManager()
; FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
;
每个事务由一系列需要一起完成的操作组成。 可以在一个事务中执行多个操作,诸如 add()
、 remove()
和 replace()
等。然后,必须调用 commit()
向 Activity 提交事务。
为了能把某个事务加入 Fragment 事务的回退栈中, 可以在 commit()
之前先调用 addToBackStack()
。这个回退栈是由 Activity 管理的,以便用户可以按下Back按钮返回之前的 Fragment 状态。
例如,下面演示了用一个 Fragment 替换另一个,并把前一个状态保存到回退栈中:
1 // 创建新的 Fragment 和事务 2 Fragment newFragment = new ExampleFragment(); 3 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 4 5 // 用该 Fragment 替换 fragment_container View 中的内容, 6 // 并把事务加入回退栈 7 transaction.replace(R.id.fragment_container, newFragment); 8 transaction.addToBackStack(null); 9 10 // 提交回退栈 11 transaction.commit();
在这个例子中,newFragment
替换了 ID 为 R.id.fragment_container
的布局容器中的当前 Fragment 。 通过调用 addToBackStack()
,这次替换操作作为事务被保存到回退栈中,这样用户就可以按下 Back 键回滚事务,回到前一个 Fragment 中。
如果在一个事务中加入了多步操作(比如更多的 add()
或 remove()
操作),并且调用了 addToBackStack()
,那么在 commit()
之前的全部这些操作都会作为同一个事务进入回退栈,按下 Back 键后将会回滚全部操作。
同一个 FragmentTransaction
中的操作与加入顺序无关,除了:
commit()
必须最后调用;- 如果在同一个容器中添加了多个 Fragment ,那么添加的顺序决定了在 View 层次结构中的显示顺序。
如果在执行移除 Fragment 事务时没有调用 addToBackStack()
,那么事务提交后该 Fragment 将会被销毁,用户就无法再返回了。 反之,如果移除前调用了 addToBackStack()
,那么 Fragment 将会被停止,当用户返回时将会被恢复。
提示: 每个 Fragment 事务都可以应用一种过渡动画效果,这通过在提交事务前调用 setTransition()
来实现。
调用 commit()
后事务并不是立即被执行的,而是被排入 Activity UI 线程(主线程)的运行计划,一有空闲就会执行。 不过在必要时,也可以调用当前 UI 线程的 executePendingTransactions()
,以便立即执行已提交的事务。 通常没必要这么做,除非有其他线程中的任务在等待该事务的完成。
警告: 只有在 Activity 保存状态 之前才能利用 commit()
来提交 Fragment 事务。 如果在之后提交会抛出异常。这是因为在恢复 Activity 时,事务提交之后的状态可能会丢失。 如果丢失提交内容也没什么关系,请使用 commitAllowingStateLoss()
。
与 Activity 通讯
虽然 Fragment
是独立于 Activity
实现的对象,且一个 Fragment 可在多个 Activity 中使用,但对某个 Fragment 实例而言,是与其所在的 Activity 绑定在一起的。
具体来说, Fragment 可以通过 getActivity()
访问 Activity
实例,很容易就能实现查找 Activity 布局中的 View 之类的任务:
View listView =getActivity()
.findViewById
(R.id.list);
同理, Activity 也可以通过 FragmentManager
的 findFragmentById()
或 findFragmentByTag()
来获取一个 Fragment
的引用。 例如:
ExampleFragment fragment =(ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
为 Activity 创建事件回调方法
某些情况下, Fragment 也许需要与 Activity 共享事件。 较好的处理方式是在 Fragment 中定义一个回调接口,并要求宿主 Activity 实现这个接口。 当 Activity 通过接口接收到某个回调方法时,可以根据需要与同一布局中的其他 Fragment 分享事件信息。
例如,某个新闻应用在一个 Activity 中包含了两个 Fragment —— 一个用于显示文章列表(Fragment A),另一个显示文章内容(Fragment B) —— 当某个列表项被选中时,Fragment A 必须把信息告诉 Activity,以便通知 Fragment B 显示文章内容。 这种情况下,就可在 Fragment A 中定义一个 OnArticleSelectedListener
接口:
1 public static class FragmentA extends ListFragment { 2 ... 3 // 容器 Activity 必须实现该接口 4 public interface OnArticleSelectedListener { 5 public void onArticleSelected(Uri articleUri); 6 } 7 ... 8 }
然后,Fragment 的宿主 Activity 实现了 OnArticleSelectedListener
接口,并重写(Override) onArticleSelected()
方法,以便将 Fragment A 的事件发送给 Fragment B。 为了确保宿主 Activity 必须实现该接口,在 Fragment A 的 onAttach()
回调方法(系统会在把 Fragment 添加到 Activity 之后调用)中,通过对传入 onAttach()
的 Activity
进行强制类型转换(Cast),尝试实例化OnArticleSelectedListener
对象:
1 public static class FragmentA extends ListFragment { 2 OnArticleSelectedListener mListener; 3 ... 4 @Override 5 public void onAttach(Activity activity) { 6 super.onAttach(activity); 7 try { 8 mListener = (OnArticleSelectedListener) activity; 9 } catch (ClassCastException e) { 10 throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener"); 11 } 12 } 13 ... 14 }
如果 Activity 没有实现接口, Fragment 将会抛出异常 ClassCastException
。 如果转换成功,成员变量mListener
中就指向了 Activity 已实现的OnArticleSelectedListener
, 这样 Fragment A 就可以通过调用OnArticleSelectedListener
接口中定义的方法,与 Activity 分享事件了。 例如,假定 Fragment A 扩展自 ListFragment
,当用户每次点击列表项时,系统就会调用 Fragment 的 onListItemClick()
方法,然后由它调用 onArticleSelected()
与 Activity 分享事件。
1 public static class FragmentA extends ListFragment { 2 OnArticleSelectedListener mListener; 3 ... 4 @Override 5 public void onListItemClick(ListView l, View v, int position, long id) { 6 // Append the clicked item's row ID with the content provider Uri 7 Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id); 8 // Send the event and Uri to the host activity 9 mListener.onArticleSelected(noteUri); 10 } 11 ... 12 }
传入onListItemClick()
的id
参数是被点击项的原始 ID, Activity(或其他 Fragment)用它从应用程序的 ContentProvider
中读取文章内容。
关于更多使用 Content Provider 的信息,请参阅文档 Content Provider。
向 Action Bar 添加菜单项
通过实现 onCreateOptionsMenu()
,Fragment 可以为 Activity 的 选项菜单(及 Action Bar)提供菜单项。 不过,要让该方法能接收到调用,必须在 onCreate()
方法中调用 setHasOptionsMenu()
,以便声明该 Fragment 将要在“选项”(Options)菜单中添加菜单项(否则,Fragment 将不会收到 onCreateOptionsMenu()
) 调用。
所有由 Fragment 添加到“选项”菜单中去的菜单项,都将追加到已有菜单项的后面。 当选中某个菜单项时, Fragment 同时还会收到 onOptionsItemSelected()
调用。
通过调用 registerForContextMenu()
,还可以把 Fragment 布局中的某个 View 注册为上下文(Context)菜单。 当用户打开此菜单时, Fragment 将会接收到一次 onCreateContextMenu()
. 而当用户选中一个菜单项时,Fragment 将收到一次 onContextItemSelected()
调用。
注意: 当用户选中某个菜单项时,虽然 Fragment 是会收到“on-item-selected”的回调,但 Activity 将首先收到相应的回调。 如果 Activity 的“on-item-selected”回调方法的代码中没有对选中项进行处理,事件才会传给 Fragment 回调。 “选项”菜单和上下文菜单都是如此。
关于菜单的更多信息,请参阅开发指南中的 菜单和 Action Bar 章节。
处理 Fragment 的生命周期
![](http://images0.cnblogs.com/blog/704105/201501/041340177638254.png)
图 3. Activity 生命周期对 Fragment 生命周期的影响
Fragment 生命周期的管理与 Activity 的非常相像。 与 Activity 类似, Fragment 可能处于三种状态:
- 恢复(Resumed)
- Fragment 在当前 Activity 中可见。
- 暂停(Paused)
- 其他 Activity 处于前台并获得焦点,但 Fragment 所在的 Activity 仍然可见 (前台 Activity 是部分透明的或者未完全遮挡整个屏幕)。
- 停止(Stopped)
- Fragment 不可见。也许是宿主 Activity 已被停止,也许 Fragment 已从 Activity 中移除且被加入回退栈中。 已被停止的 Fragment 仍然是存活的(所有的状态和成员信息都被系统保存着)。 不过,用户看不到此 Fragment 了,当 Activity 被杀死时 Fragment 也会被杀死。
与 Activity 类似,在 Activity 进程被杀死并被重新创建,且需要恢复 Fragment 的状态时, 可以利用 Bundle
取回 Fragment 的状态。 可以在 Fragment 的 onSaveInstanceState()
回调方法中保存 Fragment 的状态,并在 onCreate()
、 onCreateView()
或 onActivityCreated()
方法中恢复状态。 关于保存状态的更多信息,请参阅文档 Activity。
Activity 与 Fragment 生命周期之间的最明显区别,就是在各自的回退栈中的保存方式。 当 Activity 被停止时,它默认会被存放在系统管理的 Activity 回退栈中(如 任务和回退栈 所述,用户可以用Back键回退回去)。 然而,仅当在移除 Fragment 的事务中调用 addToBackStack()
显式地请求保存实例, Fragment 才会被放入由宿主 Activity 管理的回退栈中。
除此之外, Fragment 生命周期的管理与 Activity 非常类似。 因此,管理 Activity 生命周期 一文中介绍的做法同样也适用于 Fragment 。 当然,还有必要了解 Activity 生命周期对 Fragment 生命周期的影响。
提醒: 如果需要在 Fragment
中使用 Context
对象,可以调用 getActivity()
。 但是请注意,只能在 Fragment 附着于 Activity 之后,才能进行调用。 当 Fragment 还没有与 Activity 关联之前,或者在生命周期结束被解除关联之后, getActivity()
将会返回 null。
与 Activity 的生命周期合作
宿主 Activity 的生命周期直接影响着 Fragment 的生命周期, Activity 的所有生命周期回调方法都会触发其中每一个 Fragment 的对应回调方法。 比如,当 Activity 收到 onPause()
时,其中的每一个 Fragment 都会收到 onPause()
调用。
不过,Fragment 还拥有一些自己的生命周期回调方法,这些方法只与 Activity 有关,用于执行创建或销毁 Fragment 界面之类的操作。 这包括:
onAttach()
- 当 Fragment 与 Activity 建立关联时将会调用(这里会传入
Activity
)。 onCreateView()
- 创建与 Fragment 关联的 View 层次架构。
onActivityCreated()
- 当 Activity 的
onCreate()
方法返回时将会调用。 onDestroyView()
- 当与 Fragment 关联的 View 层次架构被删除时将会调用。
onDetach()
- 当 Fragment 与 Activity 解除关联时将会调用。
图3中给出了 Fragment 生命周期的流程,及其与 宿主 Activity 之间的关系。 从图中可知, Activity 所处的各个状态决定了 Fragment 可能收到的回调方法。 比如,当 Activity 已收到过 onCreate()
调用之后,该 Activity 中的 Fragment 将不再会收到 onActivityCreated()
调用了。
只要 Activity 进入了恢复(Resumed)状态,就可以在其中自由添加或移除 Fragment 了。 也只有在 Activity 处于已恢复状态时,Fragment 的生命周期才可以脱开 Activity 而独自变化。
不过,一旦 Activity 离开了已恢复状态, Fragment 的生命周期就又被 Activity 所左右了。
示例
以下示例将把本文所述综合在一起展示,这里的 Activity 用到两个 Fragment 创建了双板块布局。 在下面的 Activity 中,一个 Fragment 显示了莎士比亚戏剧列表,另一个 Fragment 显示了列表选中剧目的简介。 并且,还演示了如何根据屏幕配置设置 Fragment 的参数。
注意: 本例的完整源码见 FragmentLayout.java
。
按惯例,主 Activity 在 onCreate()
中应用了一个布局:
1 @Override 2 protectedvoid onCreate(Bundle savedInstanceState){ 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.fragment_layout); 6 }
布局由fragment_layout.xml
文件给出:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent"android:layout_height="match_parent"> <fragmentclass="com.example.android.apis.app.FragmentLayout$TitlesFragment" android:id="@+id/titles"android:layout_weight="1" android:layout_width="0px"android:layout_height="match_parent"/> <FrameLayoutandroid:id="@+id/details"android:layout_weight="1" android:layout_width="0px"android:layout_height="match_parent" android:background="?android:attr/detailsElementBackground"/> </LinearLayout>
只要 Activity 载入该布局文件,系统就立刻实例化了TitlesFragment
(显示剧目用的)。而 FrameLayout
(用于显示戏剧简介的 Fragment)将会占据屏幕右侧的空间,但一开始是空的。 如下所示,只有当用户在列表中选择了一项,才会在 FrameLayout
中放入一个 Fragment 。
然而,不是所有的屏幕都能同时把剧目表和简介并排显示出来。 所以上述布局只适用于横向屏幕,即保存到res/layout-land/fragment_layout.xml
。
但是,当屏幕为纵向模式,系统将会使用保存在res/layout/fragment_layout.xml
中的以下布局:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment" android:id="@+id/titles" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
该布局只包含TitlesFragment
。 这就是说,当设备处于纵向模式下,将只显示剧目列表。 因此,如果这时用户点击列表项的话,应用程序将会打开一个新 Activity 用来显示简介,而不是载入第二个 Fragment 。
下面将展示如何在 Fragment 类中实现这种显示方式。 首先是显示莎士比亚戏剧列表的TitlesFragment
。 这个 Fragment 扩展自 ListFragment
并靠它来完成大部分列表框(List View)的操作。
在查看这部分代码时请注意,当用户点击某个列表项时,可能要执行两种操作: 根据当前的布局不同,或是在同一个 Activity 中新建并显示一个展现戏剧简介的 Fragment (在 FrameLayout
中添加 Fragment),或是启动一个新的 Activity (用于显示 Fragment)。
1 public static class TitlesFragment extends ListFragment { 2 boolean mDualPane; 3 int mCurCheckPosition = 0; 4 5 @Override 6 public void onActivityCreated(Bundle savedInstanceState) { 7 super.onActivityCreated(savedInstanceState); 8 9 // 用存放剧目名称的静态数组填充列表 10 setListAdapter(new ArrayAdapter<String>(getActivity(), 11 android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES)); 12 13 // 检查以下,当前界面容器中是否存在 Frame, 14 // details Fragment 将置入其中。 15 View detailsFrame = getActivity().findViewById(R.id.details); 16 mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE; 17 18 if (savedInstanceState != null) { 19 // 恢复上次选中的位置 20 mCurCheckPosition = savedInstanceState.getInt("curChoice", 0); 21 } 22 23 if (mDualPane) { 24 // 在双板块同时显示的模式下,列表 View 高亮显示选中项。 25 getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); 26 // 确认界面状态是否正确 27 showDetails(mCurCheckPosition); 28 } 29 } 30 31 @Override 32 public void onSaveInstanceState(Bundle outState) { 33 super.onSaveInstanceState(outState); 34 outState.putInt("curChoice", mCurCheckPosition); 35 } 36 37 @Override 38 public void onListItemClick(ListView l, View v, int position, long id) { 39 showDetails(position); 40 } 41 42 /** 43 * 助手函数,用于显示选中项的内容, 44 * 或是在当前界面显示 Fragment, 45 * 或是新开一个 Activity 显示。 46 */ 47 void showDetails(int index) { 48 mCurCheckPosition = index; 49 50 if (mDualPane) { 51 // 当前可以用 Fragment 同时显示两块内容, 52 // 因此只要更新列表,把选中项高亮显示,并显示其详情。 53 getListView().setItemChecked(index, true); 54 55 // 检查当前显示的是哪个 Fragment,必要的话进行替换。 56 DetailsFragment details = (DetailsFragment) 57 getFragmentManager().findFragmentById(R.id.details); 58 if (details == null || details.getShownIndex() != index) { 59 // 新建 Fragment 显示选中项 60 details = DetailsFragment.newInstance(index); 61 62 // 提交事务, 63 // 用 Frame 中的当前 Fragment 替换原有的。 64 FragmentTransaction ft = getFragmentManager().beginTransaction(); 65 if (index == 0) { 66 ft.replace(R.id.details, details); 67 } else { 68 ft.replace(R.id.a_item, details); 69 } 70 ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 71 ft.commit(); 72 } 73 74 } else { 75 // 否则,需要启动新的, 76 // 把包含所选项内容的对话框 Fragment 显示出来。 77 Intent intent = new Intent(); 78 intent.setClass(getActivity(), DetailsActivity.class); 79 intent.putExtra("index", index); 80 startActivity(intent); 81 } 82 } 83 }
第二个 Fragment 的代码,用于显示TitlesFragment
内所选剧目的简介:
1 public static class DetailsFragment extends Fragment { 2 /** 3 * 创建 DetailsFragment 的新实例, 4 * 初始时显示文字“index”。 5 */ 6 public static DetailsFragment newInstance(int index) { 7 DetailsFragment f = new DetailsFragment(); 8 9 // 将 index 作为输入参数 10 Bundle args = new Bundle(); 11 args.putInt("index", index); 12 f.setArguments(args); 13 14 return f; 15 } 16 17 public int getShownIndex() { 18 return getArguments().getInt("index", 0); 19 } 20 21 @Override 22 public View onCreateView(LayoutInflater inflater, ViewGroup container, 23 Bundle savedInstanceState) { 24 if (container == null) { 25 // 这里存在多种布局, 26 // 其中一个不存在包含本 Fragment 的容器 Frame。 27 // Fragment 可能是从已保存的状态中创建, 28 // 但因为不会再显示出来了,所以没有理由再为其创建 View 层次结构。 29 // 请注意,这段代码不是必需的—— 30 // 可以直接运行后面的代码,创建并返回 View 层次结构; 31 // 只是它再也用不上罢了。 32 return null; 33 } 34 35 ScrollView scroller = new ScrollView(getActivity()); 36 TextView text = new TextView(getActivity()); 37 int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 38 4, getActivity().getResources().getDisplayMetrics()); 39 text.setPadding(padding, padding, padding, padding); 40 scroller.addView(text); 41 text.setText(Shakespeare.DIALOGUE[getShownIndex()]); 42 return scroller; 43 } 44 }
请回忆一下TitlesFragment
类,如果用户点击了某个列表项,且当前布局中未包含R.id.details
View (也就是DetailsFragment
),那么应用程序将会启动DetailsActivity
来显示列表项的有关内容。
下面是DetailsActivity
类,当屏幕处于纵向模式时,这里只是简单地把DetailsFragment
嵌进来而已,用于显示所选剧目的简介。
1 publicstaticclassDetailsActivityextendsActivity{ 2 3 @Override 4 protectedvoid onCreate(Bundle savedInstanceState){ 5 super.onCreate(savedInstanceState); 6 7 if(getResources().getConfiguration().orientation 8 ==Configuration.ORIENTATION_LANDSCAPE){ 9 // 如果当前屏幕处于横向模式, 10 // 对话框可以与列表同时显示,就不需要此 Activity了。 11 finish(); 12 return; 13 } 14 15 if(savedInstanceState ==null){ 16 // 在初始化过程中,插入details Fragment。 17 DetailsFragment details =newDetailsFragment(); 18 details.setArguments(getIntent().getExtras()); 19 getFragmentManager().beginTransaction().add(android.R.id.content, details).commit(); 20 } 21 } 22 }
请注意,此 Activity 在屏幕横向放置时将会自动终止,以便让主 Activity 接替它把DetailsFragment
显示在TitlesFragment
旁边。 如果用户在打开DetailsActivity
时是纵向模式,然后旋转到横向模式(这时会重新启动当前 Activity),就会发生这种情况。
关于使用 Fragment 的更多示例(以及本例的完整代码),请参阅 ApiDemos 中的 API 演示例程(可在 SDK 组件实例 )下载。