官方帮助文档之Fragments翻译

时间:2022-03-14 03:12:24

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翻译

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

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

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


创建Fragment


官方帮助文档之Fragments翻译

要创建一个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 可以做如下事情,包括:

  • 使用findFragmentById()(用于在activity布局中提供有界面的fragment)或者findFragmentByTag()获取activity中存在的fragment(用于有界面或者没有界面的fragment)。
  • 使用popBackStack()(模仿用户的BACK命令)从后台栈弹出fragment。
  • 使用addOnBackStackChangedListener()注册一个监听后台栈变化的监听器。

关于这些函数和其它的更多信息,可参考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中的顺序注意以下两点:

  • 必须要在最后调用commit()
  • 如果你正将多个fragment添加到同一个容器中,那么添加顺序决定了它们在视图层次(view hierarchy)里显示的顺序。

在执行删除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生命周期


官方帮助文档之Fragments翻译

管理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中下载可用的)。