to top

Fragments

Fragment表现Activity中用户界面的一个行为或者是一部分。你可以在一个单独的activity上把多个fragment组合成为一个多区域的UI,并且可以在多个activity中再使用。你可以认为fragment是activity的一个模块零件,它有自己的生命周期,接收它自己的输入事件,并且可以在activity运行时添加或者删除。

Fragment必须总是被嵌入到一个activity之中,并且fragment的生命周期直接受其宿主activity的生命周期的影响。例如,一旦activity被暂停,它里面所有的fragment也被暂停,一旦activity被销毁,它里面所有的fragment也被销毁。然而,当activity正在运行时(处于resumed的生命周期状态),你可以单独的操控每个fragment,比如添加或者删除。当你执行这样一项事务时,可以将它添加到后台的一个栈中,这个栈由activity管理着——activity里面的每个后台栈内容实体是fragment发生过的一条事务记录。这个后台栈允许用户通过按BACK键回退一项fragment事务(往后导航)。

当你添加一个fragment作为某个activity布局的一部分时,它就存在于这个activity视图体系内部的ViewGroup之中,并且定义了它自己的视图布局。你可以通过在activity布局文件中声明fragment,用<fragment>元素把fragment插入到activity的布局中,或者是用应用程序源码将它添加到一个存在的ViewGroup中。然而,fragment并不是一个定要作为activity布局的一部分;fragment也可以为activity隐身工作。

这份文档描述了如何使用fragment来创建你的应用程序,包括fragment在添加到后台栈时如何维护他们的状态,如何同activity和其他同属于该activity的fragment们共享事件,构建到activity的动作槽(action bar)中去,等等。


设计原理

Android在3.0这个版本中(API Level “Honeycomb”)引入了fragment的概念,主要是支持在大屏幕上更为动态和灵活的UI设计,比方说平板电脑。由于平板电脑的屏幕要比手持电话的大许多,这样就有更多的空间去组合和交换UI组件。有了fragment,你可以不必去管理视图体系的复杂变化。通过将activity的布局分割成若干个fragment,可以在运行时编辑activity的呈现,并且那些变化会被保存在由activity管理的后台栈里面。

例如,新闻应用程序将一个fragment放在左边显示文章列表,在右边用另一个fragment来显示一篇文章——两个fragment在同一个activity中并排着,并且每个fragment都有其自己的生命周期回调方法序列用以处理各自的用户输入事件。因此,用户可以在同一个activity中选择和阅读文章,而不是在一个activity中选择,在另一个activity中阅读,如图1所示。

应该将每一个fragment设计为模块化的和可复用化的activity组件。也就是说,你可以在多个activity中引用同一个fragment,因为fragment定义了它自己的布局,并且使用它本身生命周期回调的行为。这点尤为重要,因为它让你能够改变fragment组合以满足不同的屏幕尺寸。在设计同时支持平板电脑和手机的应用时,通过不同的布局配置可以复用fragments,基于可使用的屏幕空间优化用户体验。例如,在手机上,当同一个activity不能容纳更多的fragment时,可能需要通过分离fragments提供一个单区域的界面。

fragments.png

图1.示例说明如何用两个fragment在一个activity中组合两个UI模块布局的平板电脑模式,取代界面分离的手机模式。

例如——还是以那个新闻应用程序为例——当程序运行在平板尺寸屏幕设备上时,可以在Activity A中嵌入两个fragment。但是,当运行在手机尺寸屏幕上,就没有足够的空间容纳两个fragment了,因此Activity A只能引用包含文章列表的fragment,在当用户选择一篇文章时,可以启动Activity B,它包含了用来阅读文章的第二个fragment。这样,应用程序通过以不同组合的复用fragments支持了平板电脑和手机,如图1所示。

对于不同的屏幕配置以不同的fragment组合设计应用程序的更多相关资料,参考手册的支持平板电脑和手机的相关部分。


创建Fragment

Fragment lifecycle.png

要创建一个fragment,必须创建一个fragment的子类(或是继承自它的子类)。fragment类的代码看起来很像activity。它与activity一样都有回调函数,例如onCreate(),onStart(),onPause(),和onStop()。事实上,如果你正在将一个现成的Android应用转而使用Fragment来实现,可以简单的将代码从activity的回调函数移植到各自的fragment回调函数中。

一般情况下,你至少需要实现以下几个生命周期方法:

onCreate()

在创建fragment时系统会调用此方法。在实现代码中,你可以初始化想要在fragment中保持的那些必要组件,当fragment处于暂停或者停止状态之后可重新启用它们。

onCreateView()

在第一次为fragment绘制用户界面时系统会调用此方法。为fragment绘制用户界面,这个函数必须要返回所绘出的fragment的根View。如果fragment没有用户界面可以返回空。

onPause()

系统回调用该函数作为用户离开fragment的第一个预兆(尽管这并不总意味着fragment被销毁)。在当前用户会话结束之前,通常要在这里提交任何应该持久化的变化(因为用户可能不再返回)。

大部分应用程序都应该至少为每个fragment实现这三个方法,但是还有许多其他用以操纵fragment生命周期中各个阶段的回调函数。所有生命周期中的回调函数在操纵fragment生命周期一节中稍后再做讨论。

除了基类fragment,这里还有几个你可能会继承的子类:

DialogFragment

显示一个浮动的对话框。使用这个类创建对话框是使用Activity类对话框帮助方法之外的另一个不错的选择,因为你可以把fragment对话框并入到由activity管理的fragments后台栈中,允许用户返回到一个已经摒弃的fragment。

ListFragment

显示一个由适配器管理的条目列表(例如SimpleCursorAdapter),类似于ListActivity。并且提供了许多管理列表视图的函数,例如处理点击事件的onListItemClick()回调函数。

PreferenceFragment

显示一个Preference对象的体系结构列表,类似于preferenceActivity。这在为应用程序创建“设置”activity时是很实用的。



添加用户界面

fragment常被用作activity用户界面的一部分,并且将本身的布局构建到activity中去。

为了给fragment提供一个布局,你必须实现onCreateView()回调函数,在绘制fragment布局时Android系统会调用它。实现这个函数时需要返回fragment所属的根View。

注意:如果你的fragment时ListFragment的子类,默认实现从onCreateView()返回一个ListView,所以你不需要实现它。

为了从onCreateView()返回一个布局,你可以从layout resource定义的XML文件inflate它。为了便于你这样做,onCreateView()提供一个LayoutInflater对象。

例如,下面是一个fragment子类,它的布局从example_fragment.xml载入的:

	public static class ExampleFragment extends Fragment {
		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
								 Bundle savedInstanceState) {
			// Inflate the layout for this fragment
			return inflater.inflate(R.layout.example_fragment, container, false);
		}
	}

传入onCreateView()的参数container 是你的frament布局将要被插入的父ViewGroup(来自activity的布局)。如果fragment处于resumed状态(恢复状态在操纵fragment生命周期一节中将作更多讨论),参数savedInstanceState是属于Bundle类,它提供了fragment之前实例的相关数据。

inflate()函数需要以下三个参数:
  • 要inflate的布局的资源ID。
  • 被inflate的布局的父ViewGroup。传入container很重要,这是为了让系统将布局参数应用到被inflate的布局的根view中去,由其将要嵌入的父view指定。
  • 一个布尔值,表明在inflate期间被infalte的布局是否应该附上ViewGroup(第二个参数)。(在这个例子中传入的是false,因为系统已经将被inflate的布局插入到容器中(container)——传入true会在最终的布局里创建一个多余的ViewGroup。)

现在你已经知道了如何创建一个有布局的fragment。下一步,则需要将fragment添加到activity中。

将fragment添加到activity之中

通常,fragment构建了其宿主activity的部分界面,它被作为activity全部视图层次体系的一部分被嵌入进去。在acitivity布局中添加fragment有两种方法:

  • 在activity的布局文件里声明fragment
像这样,你可以像为view一样为fragment指定布局属性。例如,下面含有两个fragment的布局文件:
	<?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,并为它们调用onCreateView()函数,以获取每一个fragment的布局。系统直接在<fragment>元素的位置插入fragment返回的View。

注意:每个fragment都需要一个唯一的标识,如果重启activity,系统可用来恢复fragment(并且可用来捕捉fragment的事务处理,例如移除)。为fragment提供ID有三种方法:

  • 用android:id属性提供一个唯一的标识。
  • 用android:tag属性提供一个唯一的字符串。
  • 如果上述两个属性都没有,系统会使用其容器视图(view)的ID。
  • 或者,通过编码将fragment添加到已存在的ViewGroup中

在activity运行的任何时候,你都可以将fragment添加到activity布局中。你仅需要简单指定用来放置fragment的ViewGroup。

你应当使用FragmentTransaction的API来对activity中的fragment进行操作(例如添加,移除,或者替换fragment)。你可以像下面这样从Activity中取得FragmentTransaction的实例:

	FragmentManager fragmentManager = getFragmentManager()
	FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

可以用add()函数添加fragment,并指定要添加的fragment以及要将其插入到哪个视图(view)之中:

	ExampleFragment fragment = new ExampleFragment();
	fragmentTransaction.add(R.id.fragment_container, fragment);
	fragmentTransaction.commit();

传入add()函数的第一个参数是fragment被放置的ViewGroup,它由资源ID(resource ID)指定,第二个参数就是要添加的fragment。

一旦通过FragmentTransaction 做了更改,都应当使用commit()使变化生效。

添加无界面的fragment

上面的例子是如何将fragment添加到activity中去,目的是提供一个用户界面。然而,也可以使用fragment为activity提供后台动作,却不呈现多余的用户界面。

想要添加没有界面的fragment ,可以使用add(Fragment, String)(为fragment提供一个唯一的字符串“tag”,而不是视图(view)ID)。这样添加了fragment,但是,因为还没有关联到activity布局中的视图(view) ,收不到onCreateView()的调用。所以不需要实现这个方法。

为无界面fragment提供字符串标签并不是专门针对无界面fragment的——也可以为有界面fragment提供字符串标签——但是对于无界面fragment,字符串标签是唯一识别它的方法。如果之后想从activity中取到fragment,需要使用findFragmentByTag()。

activity使用fragment在后台运行可参考例子FragmentRetainInstance.java

管理Fragments

想要管理activity中的fragment,可以使用FragmentManager。可以通过在activity中调用getFragmentManager()获得。

使用FragmentManager 可以做如下事情,包括:

关于这些函数和其它的更多信息,可参考FragmentManager类文档。

处理Fragment事务

在activity中使用fragment的一大特点是具有添加、删除、替换,和执行其它动作的能力,以响应用户的互动。提交给activity的每一系列变化被称为事务,并且可以用FragmentTransaction 中的APIs处理。你也可以将每一个事务保存在由activity管理的后台栈中,并且允许用户导航回退fragment变更(类似于activity的导航回退)。

你可以从FragmentManager中获取FragmentTransaction实例,像这样:

 FragmentManager fragmentManager = getFragmentManager();
 FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

每项事务是在同一时间内要执行的一系列的变更。你可以为一个给定的事务用相关方法设置想要执行的所有变化,例如add(),remove(),和replace()。然后,用commit()将事务提交给activity。

然而,在调用commit()之前,为了将事务添加到fragment事务后台栈中,你可能会想调用addToBackStatck()。这个后台栈由activity管理,并且允许用户通过按BACK键回退到前一个fragment状态。

举个例子,下面的代码是如何使用另一个fragment代替一个fragment,并且将之前的状态保留在后台栈中:

 // Create new fragment and transaction
 Fragment newFragment = new ExampleFragment();
 FragmentTransaction transaction = getFragmentManager().beginTransaction();

 // Replace whatever is in the fragment_container view with this fragment,
 // and add the transaction to the back stack
 transaction.replace(R.id.fragment_container, newFragment);
 transaction.addToBackStack(null);

 // Commit the transaction
 transaction.commit();

在这个例子中,newFragment替换了当前在布局容器中用R.id.fragment_container标识的所有的fragment(如果有的话),替代的事务被保存在后台栈中,因此用户可以回退该事务,可通过按BACK键还原之前的fragment。

如果添加多个变更事务(例如另一个add()或者remove())并调用addToBackStack(),那么在调用commit()之前的所有应用的变更被作为一个单独的事务添加到后台栈中,并且BACK键可以将它们一起回退。

将变更添加到FragmentTransaction中的顺序注意以下两点:

在执行删除fragment事务时,如果没有调用addToBackStack(),那么事务一提交fragment就会被销毁,而且用户也无法回退它。然而,当移除一个fragment时,如果调用了addToBackStack(),那么之后fragment会被停止,如果用户回退,它将被恢复过来。

提示:对于每一个fragment事务,在提交之前通过调用setTransition()来应用一系列事务动作。

调用commit()并不立刻执行事务,相反,而是采取预约方式,一旦activity的界面线程(主线程)准备好便可运行起来。然而,如果有必要的话,你可以从界面线程调用executePendingTransations()立即执行由commit()提交的事务。但这样做,通常是没有必要的,除非其它线程的工作依赖与该项事务。

警告:只能在activity保存状态(当用户离开activity时)之前用commit()提交事务。如果你尝试在那时之后提交,会抛出一个异常。这是因为如果activity需要被恢复,提交后的状态会被丢失。对于这类丢失提交的情况,可使用commitAllowingStateLoss()

与Activity交互

尽管Fragment被实现为一个对象,它独立于Activity并可以在多个Activity中使用,一个给定的fragment实例直接被捆绑在包含它的Activity中。

特别是,fragment可以通过getActivity()函数访问Activity,并且很容易的执行类似于查找activity布局中的视图的任务:

 View listView = getActivity().findViewById(R.id.list);

同样的,activity能够调用fragment的函数findFragmentById()或者findFragmentByTag(),从FragmentManager中获取Fragment的索引,例如:

 ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);


创建activity事件回调函数

在一些情况下,你可能需要fragment与activity共享事件。这样做的一个好方法是在fragment内部定义一个回调接口,并需要宿主activity实现它。当activity通过接口接收到回调时,可以在必要时与布局中的其它fragment共享信息。

举个例子,如果新闻应用的actvity中有两个fragment——一个显示文章列表(fragment A),另一个显示一篇文章(fragment B)——然后fragment A 必须要告诉activity列表项何时被选种,这样,activity可以通知fragment B显示这篇文章。这种情况下,在fragment A内部声明接口OnArticleSelectedListener:

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

然后fragment的宿主activity实现了OnArticleSelectedListener接口,并且重写onArticleSelected()以通知fragment B来自于fragment A的事件。为了确保宿主activity实现了这个接口,fragment A的onAttach()回调函数(当添加fragment到activity中时系统会调用它)通过作为参数传入onAttach()的activity的类型转换来实例化一个OnArticleSelectedListener实例。

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
    ...
}

如果activity没有实现这个接口,那么fragment会抛出一个ClassCaseException异常。一旦成功,mListener成员会保留一个activity的OnArticleSelectedListener实现的引用,由此fragment A可以通过调用由OnArticleSelectedListener接口定义的方法与activity共享事件。例如,如果fragment A是ListFragment的子类,每次用户点击列表项时,系统都会调用fragment的onListItemClick()事件,然后fragment调用onArticleSelected()来与activity共享事件。

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);
    }
    ...
}

传递给onListItemClick()的参数id是点击的列表项行id,activity(或者其它fragment)用以从应用的ContentProvider获取文章。

关于使用content provider的更多资料可以在在Content Providers文档获取。

添加items到Action Bar

你的fragments可以通过实现onCreateOptionsMenu()来构建菜单项到activity的Options Menu(因此Action Bar也一样)。为了使用这个方法接收到调用,不管怎样,你必须在onCreate()期间调用setHasOptionsMenu(),来指明想要添加项目到Options Menu的那个fragment(否则,fragment将接收不到onCreateOptionsMenu()的调用)。

任何想要在fragment中的Options Menu添加的项目都追加到已有的菜单项后面。当菜单项被选中时,fragment也会接收到对onOptionsItemSelected()的回调。

你也可以通过调用registerForContextMenu()在fragment布局中注册一个view以提供一个context menu。当用户打开context menu时,fragment接收到对onCreateContextMenu()的回调。当用户选中一个项目时,fragment接收到对onContextItemSelected()的回调。

注意:尽管你的fragment会接收到为添加到每个菜单项被选择菜单项的回调,但当用户选择一个菜单项时,activity会首先接收到对应的回调。如果activity的选择菜单项回调的实现没有处理被选中的项目,那么该事件被传递给fragment的回调。这同样适用于Options Menu和context menu。

关于菜单的更多信息,参考菜单Action Bar开发指南。

处理Fragment生命周期

Activity fragment lifecycle.png

管理fragment生命周期与管理activity生命周期很相像。像activity一样,fragment也有三种状态:

Resumed

fragment在运行中的activity可见。

Paused

另一个activity处于前台且得到焦点,但是这个fragment所在的activity仍然可见(前台activity部分透明,或者没有覆盖全屏)。

Stopped

fragment不可见。要么宿主activity已经停止,要么fragment已经从activity上移除,但已被添加到后台栈中。一个停止的fragment仍然活着(所有状态和成员信息仍然由系统保留着)。但是,它对用户来讲已经不再可见,并且如果activity被杀掉,它也将被杀掉。

同activity类似的还有,你也可以用Bundle保存fragment状态,万一activity的进程被杀掉了,并且在activity被重新创建时,你需要恢复fragment状态。在回调执行fragment的onSaveInstanceState()期间可以保存状态,在onCreate(),onCreateView(),或者onActvityCreate()中可以恢复状态。更多关于保存状态的信息,参考Activities文档。

在生命周期方面,activity与fragment之间一个很重要的不同,就是在各自的后台栈中是如何存储的。当activity停止时,默认情况下,activity被安置在由系统管理的activity后台栈中(因此用户可以按BACK键回退导航,就像在Tasks和后台栈中讨论的那样)。但是,仅当你在一个事务被移除时,通过显式调用addToBackStack()请求保存的实例,该fragment才被置于由宿主activity管理的后台栈。

除此之外,管理fragment的生命周期与管理activity的生命周期非常相似。所以,管理activity生命周期的实践同样也适用于fragment。你需要了解的,仅仅是activity的生命周期如何影响fragment的的。


与activity生命周期的协调合作

fragment所生存的activity生命周期直接影响着fragment的生命周期,由此针对activity的每一个生命周期回调都会引发一个fragment类似的回调。例如,当activity接收到onPause()时,这个activity之中的每个fragment都会接收到onPause()。

Fragment有一些额外的生命周期回调方法,然而,为了处理像是创建和销毁fragment界面,它与activity进行独特的交互。这些额外的回调方法是:

onAttach()
当fragment被绑定到activity时调用(Activity会被传入)。
onCreateView()
创建与fragment相关的视图体系时被调用。
onActivityCreated()
当activity的onCreate()函数返回时被调用。
onDestroyView()
当与fragment关联的视图体系正被移除时被调用。
onDetach()
当fragment正与activity解除关联时被调用。

fragment的生命周期流程实际上是受其宿主activity影响,如图3所示。在这张图中,可以看到activity的每个连续状态是如何决定fragment可能接收到哪个回调函数的。例如,当activity接收到它的onCreate()回调时,activity之中的fragment接收到的仅仅是onActivityCreated()回调。

一旦activity处于resumed状态,则可以在activity中自由的添加或者移除fragment。因此,只有当activity处于resumed状态时,fragment的生命周期才可以独立变化。

然而,当activity离开恢复状态时,fragment再一次被activity推入它的生命周期中。

范例

为了把这篇文档里的内容放到一起讨论,这里有一个使用两个fragment创建双区域布局的activity的范例。这个activity下面包含的一个fragment用来显示莎士比亚戏剧列表,另一个fragment则用来在列表项被选中时显示该戏剧的概要。它也表明了如何基于不同屏幕配置提供不同的fragment配置。

注意:这个activity的全部源代码可以在FragmentLayout.java获取。

在onCreate()期间,主activity用常用的方法应用了一个布局。

 @Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
 
     setContentView(R.layout.fragment_layout);
 }

被应用的布局是fragment_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    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_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />
    <FrameLayout android: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用来显示即将列出的戏剧概要)消耗了内存,但是起先是空的。就像你下面将要看到的,直到用户从列表中选择一项时,fragment才被置于FrameLayout中。

然而,并不是所有的屏幕配置都足够宽,以显示并排的戏剧列表和摘要。所以,上面的布局仅适用于横向屏幕配置,且配置保存在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,并且靠它处理大部分的列表视图工作。

就像你看到的这段代码,注意当用户点击一个列表项时会产生两个可能的行为:这依赖于两个布局哪个处于激活状态,它要么创建并显示一个新的fragment以在同一个activity中显示概要内容(添加fragment到FrameLayout),要么启动一个新的activity(在fragment可显示的地方)。

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Populate list with our static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            // Restore last state for checked position.
            mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }

        if (mDualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // Make sure our UI is in the correct state.
            showDetails(mCurCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    void showDetails(int index) {
        mCurCheckPosition = index;

        if (mDualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            getListView().setItemChecked(index, true);

            // Check what fragment is currently shown, replace if needed.
            DetailsFragment details = (DetailsFragment)
                    getFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index);

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.replace(R.id.details, details);
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}

第二个fragment,DetailsFragment在TitlesFragment的列表项被选中时显示戏剧概要。

public static class DetailsFragment extends Fragment {
    /**
     * Create a new instance of DetailsFragment, initialized to
     * show the text at 'index'.
     */
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist.  The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed.  Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}


从TitlesFragment类回想一下,如果用户点击列表项,并且目前的区域不包含R.id.details视图(这是DetailsFragment的所在),那么应用程序启动DetailsActivity以显示列表项内容。

下面是DetailsActivity,它只是在屏幕纵向时,嵌入DetailFragment以显示被选中的戏剧概要。

public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}


请注意,如果配置是横向的,这个activity会结束掉自己,使主acitivity可以接管并在TitlesFragment旁显示DetailsFragment。这种情况可能会发生在,用户在纵向时开启了DetailsActivity,但是之后旋转到横向(它重新启动了当前activity)。

更多的关于使用fragment的例子(还有这个例子的完整源码),请参阅ApiDemos中的示例代码(从 Samples SDK component中下载可用的)。

下面是部分原文,请选择性阅读:

Fragments

Quickview

  • Fragments decompose application functionality and UI into reusable modules
  • Add multiple fragments to a screen to avoid switching activities
  • Fragments have their own lifecycle, state, and back stack
  • Fragments require API Level 11 or greater

In this document

  1. Design Philosophy
  2. Creating a Fragment
    1. Adding a user interface
    2. Adding a fragment to an activity
  3. Managing Fragments
  4. Performing Fragment Transactions
  5. Communicating with the Activity
    1. Creating event callbacks to the activity
    2. Adding items to the Action Bar
  6. Handling the Fragment Lifecycle
    1. Coordinating with the activity lifecycle
  7. Example

Key classes

  1. Fragment
  2. FragmentManager
  3. FragmentTransaction

Related samples

  1. Honeycomb Gallery
  2. ApiDemos

See also

  1. Supporting Tablets and Handsets

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running (sort of like a "sub activity" that you can reuse in different activities).

A fragment must always be embedded in an activity and the fragment's lifecycle is directly affected by the host activity's lifecycle. For example, when the activity is paused, so are all fragments in it, and when the activity is destroyed, so are all fragments. However, while an activity is running (it is in the resumed lifecycle state), you can manipulate each fragment independently, such as add or remove them. When you perform such a fragment transaction, you can also add it to a back stack that's managed by the activity—each back stack entry in the activity is a record of the fragment transaction that occurred. The back stack allows the user to reverse a fragment transaction (navigate backwards), by pressing the Back button.

When you add a fragment as a part of your activity layout, it lives in a ViewGroup inside the activity's view hierarchy and the fragment defines its own view layout. You can insert a fragment into your activity layout by declaring the fragment in the activity's layout file, as a <fragment> element, or from your application code by adding it to an existing ViewGroup. However, a fragment is not required to be a part of the activity layout; you may also use a fragment without its own UI as an invisible worker for the activity.

This document describes how to build your application to use fragments, including how fragments can maintain their state when added to the activity's back stack, share events with the activity and other fragments in the activity, contribute to the activity's action bar, and more.

Design Philosophy

Android introduced fragments in Android 3.0 (API level 11), primarily to support more dynamic and flexible UI designs on large screens, such as tablets. Because a tablet's screen is much larger than that of a handset, there's more room to combine and interchange UI components. Fragments allow such designs without the need for you to manage complex changes to the view hierarchy. By dividing the layout of an activity into fragments, you become able to modify the activity's appearance at runtime and preserve those changes in a back stack that's managed by the activity.

For example, a news application can use one fragment to show a list of articles on the left and another fragment to display an article on the right—both fragments appear in one activity, side by side, and each fragment has its own set of lifecycle callback methods and handle their own user input events. Thus, instead of using one activity to select an article and another activity to read the article, the user can select an article and read it all within the same activity, as illustrated in the tablet layout in figure 1.

You should design each fragment as a modular and reusable activity component. That is, because each fragment defines its own layout and its own behavior with its own lifecycle callbacks, you can include one fragment in multiple activities, so you should design for reuse and avoid directly manipulating one fragment from another fragment. This is especially important because a modular fragment allows you to change your fragment combinations for different screen sizes. When designing your application to support both tablets and handsets, you can reuse your fragments in different layout configurations to optimize the user experience based on the available screen space. For example, on a handset, it might be necessary to separate fragments to provide a single-pane UI when more than one cannot fit within the same activity.

Figure 1. An example of how two UI modules defined by fragments can be combined into one activity for a tablet design, but separated for a handset design.

For example—to continue with the news application example—the application can embed two fragments in Activity A, when running on a tablet-sized device. However, on a handset-sized screen, there's not enough room for both fragments, so Activity A includes only the fragment for the list of articles, and when the user selects an article, it starts Activity B, which includes the second fragment to read the article. Thus, the application supports both tablets and handsets by reusing fragments in different combinations, as illustrated in figure 1.

For more information about designing your application with different fragment combinations for different screen configurations, see the guide to Supporting Tablets and Handsets.

Creating a Fragment

Figure 2. The lifecycle of a fragment (while its activity is running).

To create a fragment, you must create a subclass of Fragment (or an existing subclass of it). The Fragment class has code that looks a lot like an Activity. It contains callback methods similar to an activity, such as onCreate(), onStart(), onPause(), and onStop(). In fact, if you're converting an existing Android application to use fragments, you might simply move code from your activity's callback methods into the respective callback methods of your fragment.

Usually, you should implement at least the following lifecycle methods:

onCreate()
The system calls this when creating the fragment. Within your implementation, you should initialize essential components of the fragment that you want to retain when the fragment is paused or stopped, then resumed.
onCreateView()
The system calls this when it's time for the fragment to draw its user interface for the first time. To draw a UI for your fragment, you must return a View from this method that is the root of your fragment's layout. You can return null if the fragment does not provide a UI.
onPause()
The system calls this method as the first indication that the user is leaving the fragment (though it does not always mean the fragment is being destroyed). This is usually where you should commit any changes that should be persisted beyond the current user session (because the user might not come back).

Most applications should implement at least these three methods for every fragment, but there are several other callback methods you should also use to handle various stages of the fragment lifecycle. All the lifecycle callback methods are discussed more later, in the section about Handling the Fragment Lifecycle.

There are also a few subclasses that you might want to extend, instead of the base Fragment class:

DialogFragment
Displays a floating dialog. Using this class to create a dialog is a good alternative to using the dialog helper methods in the Activity class, because you can incorporate a fragment dialog into the back stack of fragments managed by the activity, allowing the user to return to a dismissed fragment.
ListFragment
Displays a list of items that are managed by an adapter (such as a SimpleCursorAdapter), similar to ListActivity. It provides several methods for managing a list view, such as the onListItemClick() callback to handle click events.
PreferenceFragment
Displays a hierarchy of Preference objects as a list, similar to PreferenceActivity. This is useful when creating a "settings" activity for your application.

Adding a user interface

A fragment is usually used as part of an activity's user interface and contributes its own layout to the activity.

To provide a layout for a fragment, you must implement the onCreateView() callback method, which the Android system calls when it's time for the fragment to draw its layout. Your implementation of this method must return a View that is the root of your fragment's layout.

Note: If your fragment is a subclass of ListFragment, the default implementation returns a ListView from onCreateView(), so you don't need to implement it.

To return a layout from onCreateView(), you can inflate it from a layout resource defined in XML. To help you do so, onCreateView() provides a LayoutInflater object.

For example, here's a subclass of Fragment that loads a layout from the example_fragment.xml file:

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

The container parameter passed to onCreateView() is the parent ViewGroup (from the activity's layout) in which your fragment layout will be inserted. The savedInstanceState parameter is a Bundle that provides data about the previous instance of the fragment, if the fragment is being resumed (restoring state is discussed more in the section about Handling the Fragment Lifecycle).

The inflate() method takes three arguments:

  • The resource ID of the layout you want to inflate.
  • The ViewGroup to be the parent of the inflated layout. Passing the container is important in order for the system to apply layout parameters to the root view of the inflated layout, specified by the parent view in which it's going.
  • A boolean indicating whether the inflated layout should be attached to the ViewGroup (the second parameter) during inflation. (In this case, this is false because the system is already inserting the inflated layout into the container—passing true would create a redundant view group in the final layout.)

Now you've seen how to create a fragment that provides a layout. Next, you need to add the fragment to your activity.

Adding a fragment to an activity

Usually, a fragment contributes a portion of UI to the host activity, which is embedded as a part of the activity's overall view hierarchy. There are two ways you can add a fragment to the activity layout:

  • Declare the fragment inside the activity's layout file.

    In this case, you can specify layout properties for the fragment as if it were a view. For example, here's the layout file for an activity with two fragments:

    <?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>
    

    The android:name attribute in the <fragment> specifies the Fragment class to instantiate in the layout.

    When the system creates this activity layout, it instantiates each fragment specified in the layout and calls the onCreateView() method for each one, to retrieve each fragment's layout. The system inserts the View returned by the fragment directly in place of the <fragment> element.

    Note: Each fragment requires a unique identifier that the system can use to restore the fragment if the activity is restarted (and which you can use to capture the fragment to perform transactions, such as remove it). There are three ways to provide an ID for a fragment:

    • Supply the android:id attribute with a unique ID.
    • Supply the android:tag attribute with a unique string.
    • If you provide neither of the previous two, the system uses the ID of the container view.
  • Or, programmatically add the fragment to an existing ViewGroup.

    At any time while your activity is running, you can add fragments to your activity layout. You simply need to specify a ViewGroup in which to place the fragment.

    To make fragment transactions in your activity (such as add, remove, or replace a fragment), you must use APIs from FragmentTransaction. You can get an instance of FragmentTransaction from your Activity like this:

    FragmentManager fragmentManager = getFragmentManager()
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    

    You can then add a fragment using the add() method, specifying the fragment to add and the view in which to insert it. For example:

    ExampleFragment fragment = new ExampleFragment();
    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
    

    The first argument passed to add() is the ViewGroup in which the fragment should be placed, specified by resource ID, and the second parameter is the fragment to add.

    Once you've made your changes with FragmentTransaction, you must call commit() for the changes to take effect.

Adding a fragment without a UI

The examples above show how to add a fragment to your activity in order to provide a UI. However, you can also use a fragment to provide a background behavior for the activity without presenting additional UI.

To add a fragment without a UI, add the fragment from the activity using add(Fragment, String) (supplying a unique string "tag" for the fragment, rather than a view ID). This adds the fragment, but, because it's not associated with a view in the activity layout, it does not receive a call to onCreateView(). So you don't need to implement that method.

Supplying a string tag for the fragment isn't strictly for non-UI fragments—you can also supply string tags to fragments that do have a UI—but if the fragment does not have a UI, then the string tag is the only way to identify it. If you want to get the fragment from the activity later, you need to use findFragmentByTag().

For an example activity that uses a fragment as a background worker, without a UI, see the FragmentRetainInstance.java sample.

Managing Fragments

To manage the fragments in your activity, you need to use FragmentManager. To get it, call getFragmentManager() from your activity.

Some things that you can do with FragmentManager include:

For more information about these methods and others, refer to the FragmentManager class documentation.

As demonstrated in the previous section, you can also use FragmentManager to open a FragmentTransaction, which allows you to perform transactions, such as add and remove fragments.

Performing Fragment Transactions

A great feature about using fragments in your activity is the ability to add, remove, replace, and perform other actions with them, in response to user interaction. Each set of changes that you commit to the activity is called a transaction and you can perform one using APIs in FragmentTransaction. You can also save each transaction to a back stack managed by the activity, allowing the user to navigate backward through the fragment changes (similar to navigating backward through activities).

You can acquire an instance of FragmentTransaction from the FragmentManager like this:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

Each transaction is a set of changes that you want to perform at the same time. You can set up all the changes you want to perform for a given transaction using methods such as add(), remove(), and replace(). Then, to apply the transaction to the activity, you must call commit().

Before you call commit(), however, you might want to call addToBackStack(), in order to add the transaction to a back stack of fragment transactions. This back stack is managed by the activity and allows the user to return to the previous fragment state, by pressing the Back button.

For example, here's how you can replace one fragment with another, and preserve the previous state in the back stack:

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

In this example, newFragment replaces whatever fragment (if any) is currently in the layout container identified by the R.id.fragment_container ID. By calling addToBackStack(), the replace transaction is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Back button.

If you add multiple changes to the transaction (such as another add() or remove()) and call addToBackStack(), then all changes applied before you call commit() are added to the back stack as a single transaction and the Back button will reverse them all together.

The order in which you add changes to a FragmentTransaction doesn't matter, except:

  • You must call commit() last
  • If you're adding multiple fragments to the same container, then the order in which you add them determines the order they appear in the view hierarchy

If you do not call addToBackStack() when you perform a transaction that removes a fragment, then that fragment is destroyed when the transaction is committed and the user cannot navigate back to it. Whereas, if you do call addToBackStack() when removing a fragment, then the fragment is stopped and will be resumed if the user navigates back.

Tip: For each fragment transaction, you can apply a transition animation, by calling setTransition() before you commit.

Calling commit() does not perform the transaction immediately. Rather, it schedules it to run on the activity's UI thread (the "main" thread) as soon as the thread is able to do so. If necessary, however, you may call executePendingTransactions() from your UI thread to immediately execute transactions submitted by commit(). Doing so is usually not necessary unless the transaction is a dependency for jobs in other threads.

Caution: You can commit a transaction using commit() only prior to the activity saving its state (when the user leaves the activity). If you attempt to commit after that point, an exception will be thrown. This is because the state after the commit can be lost if the activity needs to be restored. For situations in which its okay that you lose the commit, use commitAllowingStateLoss().

Communicating with the Activity

Although a Fragment is implemented as an object that's independent from an Activity and can be used inside multiple activities, a given instance of a fragment is directly tied to the activity that contains it.

Specifically, the fragment can access the Activity instance with getActivity() and easily perform tasks such as find a view in the activity layout:

View listView = getActivity().findViewById(R.id.list);

Likewise, your activity can call methods in the fragment by acquiring a reference to the Fragment from FragmentManager, using findFragmentById() or findFragmentByTag(). For example:

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

Creating event callbacks to the activity

In some cases, you might need a fragment to share events with the activity. A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary.

For example, if a news application has two fragments in an activity—one to show a list of articles (fragment A) and another to display an article (fragment B)—then fragment A must tell the activity when a list item is selected so that it can tell fragment B to display the article. In this case, the OnArticleSelectedListener interface is declared inside fragment A:

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

Then the activity that hosts the fragment implements the OnArticleSelectedListener interface and overrides onArticleSelected() to notify fragment B of the event from fragment A. To ensure that the host activity implements this interface, fragment A's onAttach() callback method (which the system calls when adding the fragment to the activity) instantiates an instance of OnArticleSelectedListener by casting the Activity that is passed into onAttach():

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
    ...
}

If the activity has not implemented the interface, then the fragment throws a ClassCastException. On success, the mListener member holds a reference to activity's implementation of OnArticleSelectedListener, so that fragment A can share events with the activity by calling methods defined by the OnArticleSelectedListener interface. For example, if fragment A is an extension of ListFragment, each time the user clicks a list item, the system calls onListItemClick() in the fragment, which then calls onArticleSelected() to share the event with the activity:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);
    }
    ...
}

The id parameter passed to onListItemClick() is the row ID of the clicked item, which the activity (or other fragment) uses to fetch the article from the application's ContentProvider.

More information about using a content provider is available in the Content Providers document.

Adding items to the Action Bar

Your fragments can contribute menu items to the activity's Options Menu (and, consequently, the Action Bar) by implementing onCreateOptionsMenu(). In order for this method to receive calls, however, you must call setHasOptionsMenu() during onCreate(), to indicate that the fragment would like to add items to the Options Menu (otherwise, the fragment will not receive a call to onCreateOptionsMenu()).

Any items that you then add to the Options Menu from the fragment are appended to the existing menu items. The fragment also receives callbacks to onOptionsItemSelected() when a menu item is selected.

You can also register a view in your fragment layout to provide a context menu by calling registerForContextMenu(). When the user opens the context menu, the fragment receives a call to onCreateContextMenu(). When the user selects an item, the fragment receives a call to onContextItemSelected().

Note: Although your fragment receives an on-item-selected callback for each menu item it adds, the activity is first to receive the respective callback when the user selects a menu item. If the activity's implementation of the on-item-selected callback does not handle the selected item, then the event is passed to the fragment's callback. This is true for the Options Menu and context menus.

For more information about menus, see the Menus and Action Bar developer guides.

Handling the Fragment Lifecycle

Figure 3. The effect of the activity lifecycle on the fragment lifecycle.

Managing the lifecycle of a fragment is a lot like managing the lifecycle of an activity. Like an activity, a fragment can exist in three states:

Resumed
The fragment is visible in the running activity.
Paused
Another activity is in the foreground and has focus, but the activity in which this fragment lives is still visible (the foreground activity is partially transparent or doesn't cover the entire screen).
Stopped
The fragment is not visible. Either the host activity has been stopped or the fragment has been removed from the activity but added to the back stack. A stopped fragment is still alive (all state and member information is retained by the system). However, it is no longer visible to the user and will be killed if the activity is killed.

Also like an activity, you can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment's onSaveInstanceState() callback and restore it during either onCreate(), onCreateView(), or onActivityCreated(). For more information about saving state, see the Activities document.

The most significant difference in lifecycle between an activity and a fragment is how one is stored in its respective back stack. An activity is placed into a back stack of activities that's managed by the system when it's stopped, by default (so that the user can navigate back to it with the Back button, as discussed in Tasks and Back Stack). However, a fragment is placed into a back stack managed by the host activity only when you explicitly request that the instance be saved by calling addToBackStack() during a transaction that removes the fragment.

Otherwise, managing the fragment lifecycle is very similar to managing the activity lifecycle. So, the same practices for managing the activity lifecycle also apply to fragments. What you also need to understand, though, is how the life of the activity affects the life of the fragment.

Coordinating with the activity lifecycle

The lifecycle of the activity in which the fragment lives directly affects the lifecycle of the fragment, such that each lifecycle callback for the activity results in a similar callback for each fragment. For example, when the activity receives onPause(), each fragment in the activity receives onPause().

Fragments have a few extra lifecycle callbacks, however, that handle unique interaction with the activity in order to perform actions such as build and destroy the fragment's UI. These additional callback methods are:

onAttach()
Called when the fragment has been associated with the activity (the Activity is passed in here).
onCreateView()
Called to create the view hierarchy associated with the fragment.
onActivityCreated()
Called when the activity's onCreate() method has returned.
onDestroyView()
Called when the view hierarchy associated with the fragment is being removed.
onDetach()
Called when the fragment is being disassociated from the activity.

The flow of a fragment's lifecycle, as it is affected by its host activity, is illustrated by figure 3. In this figure, you can see how each successive state of the activity determines which callback methods a fragment may receive. For example, when the activity has received its onCreate() callback, a fragment in the activity receives no more than the onActivityCreated() callback.

Once the activity reaches the resumed state, you can freely add and remove fragments to the activity. Thus, only while the activity is in the resumed state can the lifecycle of a fragment change independently.

However, when the activity leaves the resumed state, the fragment again is pushed through its lifecycle by the activity.

Example

To bring everything discussed in this document together, here's an example of an activity using two fragments to create a two-pane layout. The activity below includes one fragment to show a list of Shakespeare play titles and another to show a summary of the play when selected from the list. It also demonstrates how to provide different configurations of the fragments, based on the screen configuration.

Note: The complete source code for this activity is available in FragmentLayout.java.

The main activity applies a layout in the usual way, during onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.fragment_layout);
}

The layout applied is fragment_layout.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    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_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent"
            android:background="?android:attr/detailsElementBackground" />

</LinearLayout>

Using this layout, the system instantiates the TitlesFragment (which lists the play titles) as soon as the activity loads the layout, while the FrameLayout (where the fragment for showing the play summary will go) consumes space on the right side of the screen, but remains empty at first. As you'll see below, it's not until the user selects an item from the list that a fragment is placed into the FrameLayout.

However, not all screen configurations are wide enough to show both the list of plays and the summary, side by side. So, the layout above is used only for the landscape screen configuration, by saving it at res/layout-land/fragment_layout.xml.

Thus, when the screen is in portrait orientation, the system applies the following layout, which is saved at 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>

This layout includes only TitlesFragment. This means that, when the device is in portrait orientation, only the list of play titles is visible. So, when the user clicks a list item in this configuration, the application will start a new activity to show the summary, instead of loading a second fragment.

Next, you can see how this is accomplished in the fragment classes. First is TitlesFragment, which shows the list of Shakespeare play titles. This fragment extends ListFragment and relies on it to handle most of the list view work.

As you inspect this code, notice that there are two possible behaviors when the user clicks a list item: depending on which of the two layouts is active, it can either create and display a new fragment to show the details in the same activity (adding the fragment to the FrameLayout), or start a new activity (where the fragment can be shown).

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Populate list with our static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            // Restore last state for checked position.
            mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }

        if (mDualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // Make sure our UI is in the correct state.
            showDetails(mCurCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    void showDetails(int index) {
        mCurCheckPosition = index;

        if (mDualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            getListView().setItemChecked(index, true);

            // Check what fragment is currently shown, replace if needed.
            DetailsFragment details = (DetailsFragment)
                    getFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index);

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.replace(R.id.details, details);
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}

The second fragment, DetailsFragment shows the play summary for the item selected from the list from TitlesFragment:

public static class DetailsFragment extends Fragment {
    /**
     * Create a new instance of DetailsFragment, initialized to
     * show the text at 'index'.
     */
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist.  The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed.  Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}

Recall from the TitlesFragment class, that, if the user clicks a list item and the current layout does not include the R.id.details view (which is where the DetailsFragment belongs), then the application starts the DetailsActivity activity to display the content of the item.

Here is the DetailsActivity, which simply embeds the DetailsFragment to display the selected play summary when the screen is in portrait orientation:

public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}

Notice that this activity finishes itself if the configuration is landscape, so that the main activity can take over and display the DetailsFragment alongside the TitlesFragment. This can happen if the user begins the DetailsActivity while in portrait orientation, but then rotates to landscape (which restarts the current activity).

For more samples using fragments (and complete source files for this example), see the sample code available in ApiDemos (available for download from the Samples SDK component).