备注:来源参考http://developer.android.com/guide/topics/fundamentals/fragments.html
碎片(Fragments)
一个碎片代表着活动内的一个行为或者是一个用户界面的一部分。你可以在一个单独的活动内组合多个碎片来构建一个多窗口UI或者在多个活动内重用一个碎片。也可以把碎片看做是模块化的活动,碎片有它自己的生命周期,接受它自己的输入事件,并且在活动运行行添加或移除它们(有点类似于"子活动",你可以在不同的活动力重用它)。
碎片必须嵌入在一个活动里,并且他们的生命周期受到宿主活动生命周期的直接影响。例如,当宿主活动暂停时,所有活动内的碎片也处于暂停,当宿主活动被销毁时,所有的碎片也被销毁。然而,当宿主活动处于运行状态时(指它处于一个可被恢复的生命周期状态),你可以独立地控制每个碎片,譬如添加或删除它们。当我们执行此类碎片的事务时,你也可以将碎片添加到一个由宿主活动管理的"后退堆栈"里。宿主活动内的每一个"后退堆栈"都是碎片已发生过的事务记录。"后退堆栈"允许用户通过按下“后退”键来倒转一个碎片的事务(向后浏览)。
当添加一个碎片作为活动布局的一部分时,它就位于了容纳宿主活动视图层次的视图组内,并且碎片可以定义自己的视图布局。可以通过在活动布局文件内声明<fragment>
元素而将其插入到活动布局内,或者通过代码将其加入到一个现有的视图组内。然而,一个碎片不必作为活动布局的一部分而存在。你可以把不拥有UI的碎片作为活动的一个不可见协助者。
这片文章将描述如何在自己的应用中使用碎片,包括碎片在被添加到宿主活动"后退堆栈"时,及与活动和其他的碎片分享事件时,它们是如何维护自己的状态,以及如何促成活动的动作条,或更多。
碎片的概念是由Android 3.0引入的,主要的目的是为了在大屏幕设备上支持更多动态的和灵活的UI设计,例如在平板电脑上。因为平板电脑的屏幕比手机的屏幕要大,因而在手机屏幕上没有足够的空间来组合和交换UI组件。碎片则允许你如此设计而不必去管理视图层次的复杂变化。通过将活动布局分割为多个碎片,你便可以在运行时改变活动的呈现,并可以把那些变化保存在一个"后退堆栈"里。
例如,一个应用使用一个碎片在屏幕左测展示文章列表,另一个碎片在屏幕右侧展示文章内容,而这两个碎片并排地出现在一个活动里,平且每个碎片都有它自己的生命周期回调方法和处理它们各自输入事件的集合。因此, 用户能够在同一个活动内选择和浏览一篇文章,而不必用一个活动选择一个文章,用另一个活动来展示文章内容,在平板电脑上的布局如图(1)所示:
图(1)
此图展示了对于一个平板设计来说,由碎片定义的两个UI模块是如何被整合到一个活动里,而在手机上则是分开的。
你可以把碎片设计成一个模块化的,可重用的活动组件。也就是说,因为每个碎片定义了它自己的布局,行为及生命周期回调函数,因此你可以在多个活动中包括同一个碎片,所以你应该设计可重用的碎片,并避免在一个碎片直接地维护另一个碎片。这一点特别地重要,因为一个模块化的碎片允许你改变碎片组合来适应不同尺寸的屏幕。当你在设计同时支持平板电脑和手机设备的应用时,你可以根据不同的布局配置来重用你的碎片,这样就可以在可利用的屏幕空间内优化用户的体验。例如,以手机设备为例,当一个活动内一个碎片不能满足需要时,此时可能需要分离碎片来提供一个单独的窗口UI。
继续以图(1)的应用为例,当应用在平板电脑大小尺寸的设备上运行时,应用可以包含两个碎片在活动A中。然而,在手机屏幕大小的设备上,没有足够的空间容纳两个碎片,所以,在活动A里只包含了一个碎片用来显示文章列表,当用户选择一个文章时便会启动活动B,该活动里包括第二碎片用来展示文章内容。因此,如图(1)所示,通过不同的组合来重用碎片可以使得应用同时支持平板和手机两种设备。
更多关于为支持不同屏幕配置而为应用设计不同的碎片组合的信息,详见Supporting Tablets and Handsets.
创建一个碎片(Creating a Fragment)
为了创建一个碎片,你必须创建一个Fragment的子类(或是一个已存在的Fragment子类的子类)。Fragment类的代码看上去像一个Activity。它包括了类似于活动的回调函数,譬如onCreate()
,onStart()
,onPause()
, andonStop()
等。事实上,如果你把一个现有的Android应用转换成使用碎片的应用,你可能只是简单地把活动回调方法内的代码转移到对应的碎片回调方法内即可。
通常,你应该至少实现以下的生命周期方法:
onCreate
()
当创建碎片时系统调用此方法。在你的实现内部,应该初始化必要的碎片组件,它们是你想在碎片被暂停或停止时,后又要被恢复而保存的组件。
onCreateView()
当碎片第一次绘制它的用户界面时由系统调用该方法。为了给你的碎片绘制一个UI,你必须在从这个方法里返回一个视图(View),这也是你的碎片布局的根。如果碎片没有提供一个UI,可以返回NULL。
onPause()
系统调用该方法作为用户正在离开碎片(尽管这不常常意味着碎片正在被销毁)的第一个标志。该方法常常是你应该提交任何变动的地方,这些变动应该在当前用户对话之外而被持久地保存下来(因为用户可能不再返回到当前对对话)。
绝大多数的应用应该为每个碎片至少实现这三个方法, 但是还有几个回调方法可供你使用来处理碎片生命周期的各个阶段。所有的回调方法在稍后关于处理碎片生命周期(Handling the Fragment Lifecycle)的章节里进行介绍。
图(2)碎片的生命周期(当活动运行时)
这里几个可以扩展的子类,而不用以Fragment
为基类:
DialogFragment
显示为一浮动的对话框。在活动类内,使用它创建一个对话框是个很好的选择,因为你可以把一个碎片对话框
ListFragment
显示为一个由适配器(诸如一个SimpleCursorAdapter
)管理的列表项, 类似于ListActivity。它提供了几个用来管理列表视图的方法,诸如用onListItemClick()
回调来处理点击事件。
PreferenceFragment
作为列表来展示首选对象的层级结构,类似于PreferenceActivity
。当为你的应用创建"设置"活动时,它十分有用。
一个碎片常常被用作宿主活动的用户界面的一部分,因为它为宿主活动贡献出它自己的布局。
为给碎片提供一个布局,你必须实现onCreateView()
回调方法,在碎片绘制自己布局时用系统调用。onCreateView()
方法的实现必须返回一个View,它是碎片布局的根。
提醒: 如果你的碎片是ListFragment
的子类,默认的onCreateView()
实现已返回一个
ListView
,所以你不必再去实现该方法。
为了从onCreateView()
返回一个布局(即视图),你可以从一个以XML定义的布局资源中进行扩充。为帮助你这样做,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参数是父视图(来自活动布局),你的碎片布局将被
嵌入到这里。savedInstanceState
参数是一个Bundle
,如果碎片将来被恢复时(在处理碎片生命周期章节有更多的关于恢复状态的介绍),它提供了关于上一个碎片实例的相关数据。
inflate方法有三个参数:
ViewGroup
(第二个参数)。(这种情况系,参数是false,因为系统已经把扩充的布局插入到container
内,而传递true将在最终的布局上创建一个多余的视图组)。
现在,你已经看到了如何创建一个提供了布局的碎片。接下来,你需要把布局添加到你的活动中。
通常,碎片把UI的一部分贡献给他的宿主活动,这部分UI视作为宿主活动整体视图层级结构的一部分被嵌入到活动中。 这里有两种方法可以把碎片加入到活动布局里。
1. 在活动的布局文件里声明碎片
在这种情况下,你能为碎片指定布局属性,好像它是一个视图。例如,这里有一个带有连个碎片的活动布局文件。
<?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属性指定了在布局里实例化的碎片类。当系统创建这个活动布局时,它初始化每一个在布局内指定的碎片,并分别调用它们的
onCreateView()
方法来取得每个碎片的布局。然后,系统把由碎片返回的视图直接插入到<fragment>元素的位置。
提醒:每个碎片需要一个独一无二的标示符,如果活动被重新启动时,系统可以通过它来恢复碎片(同时,你也可以使用它来捕获碎片来执行事务,例如移除它)。这有三种方式可为碎片提供ID:
android:tag
属性提供一个独一无二的字符串标记.为了在活动里执行碎片事务操作(诸如,添加,移除,替换),你必须使用来自FragmentTransaction
的APIs。在你的活动里,你可以像这样来获取一个FragmentTransaction
的实例:
FragmentManager fragmentManager = getFragmentManager() FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
add()
方法来添加碎片,
需要指定将要添加的碎片及碎片插入的视图。例如:
ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit();
传递给add()
的第一个参数是碎片应该被放置的视图组,由资源ID指定,第二个参数是将要添加的碎片。一旦通过FragmentTransaction
完成了你的改变,必须调用commit()
以保证改变生效。
上面的例子演示了如何为了提供一个UI而向你的活动中添加碎片。然而,你也可以使用碎片为活动提供一个后台行为而不必呈现出额外的UI。
为了添加一个没有UI的碎片,在活动内使用add(Fragment, String)
。它添加了碎片,但是,因为在活动布局里没有视图与它有关联,因此碎片不会收到方法
(为碎片提供一个独一无二的字符串“标记”,而不是一个视图ID)onCreateView()
调用。所以,不必去实现这个方法。
给碎片提供一个字符串标记并不是严格为没有UI的碎片的,你也可以为那些有UI的碎片提供字符串标记,但是,如果碎片UI,那么字符串标记则是唯一辨识它的方法。如果想事后在活动里获取碎片,你需要使用findFragmentByTag()
方法。
有个例子,活动使用碎片作为后台协助者,它没有UI,详见FragmentRetainInstance.java
。
管理碎片(Managing Fragments)
为在活动里管理碎片,你需要使用FragmentManager
。为了得到它,在活动内调用getFragmentManager()
方法。
利用FragmentManager
,你可以做的事情包括:
findFragmentById()
方法(针对于那些提供了UI的碎片),或者调用findFragmentByTag()
方法(针对那些有或没有提供UI的碎片)。 popBackStack()
方法(模拟用户的返回(Back)命令)。addOnBackStackChangedListener()方法
来注册。
更多关于这些方法或其他方面的信息,参考FragmentManager
类。
如上一章节所述, 你可以使用FragmentManager
打开一个FragmentTransaction
(碎片事务),它允许你执行碎片事务,诸如添加和删除碎片。
执行碎片事务(Performing Fragment Transactions)
在活动里使用碎片的一个很大特点是,作为对用户交互的反应,你能够添加,移除,替换以及通过碎片执行其他的行为。你提交给活动的每一个变化集被称作"一次事务",你可以使用FragmentTransaction
里的API执行一次事务。你还能把每个事务保存到由活动管理的"后退堆栈"里,允许用户向后浏览以遍及碎片曾发生过的变化(类似于贯穿于活动的向后浏览一样)。
你能够从 FragmentManager
中取得一个FragmentTransaction
的实例,像这样:
FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每个事务就是一个你想同时执行的改变集合。你可以使用诸如add()
,remove()
和replace()
的方法来为一个给定的事务组织所有你想要执行的改变。然后,为了把事务应用到活动,你必须调用commit()
方法。
然后,在调用commit()
前,你可能想调用addToBackStack()
方法以把此次事务添加到碎片事务后退堆栈里。这个后退堆栈由活动来管理,并允许用户通过按下"Back"按钮回到先前的碎片状态。
这里有一个告诉你如何用一个碎片替换另一个,并把先前的状态保存到后退堆栈里的例子:
// 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
标示的布局容器内的碎片。通过调用addToBackStack()
,"取代事务"被保存到了后退堆栈里,所以用户能够反转事务,并通过按下"Back"按钮取回先前的碎片。
如果你在事务中添加了多个改变(比如其他的add()
或remove()
),并调用addToBackStack()
, 那么在你调用commit()
前,所有被实施的改变作为单个事务被添加到了后退堆栈里,且"Back"按钮会将他们一起反转。
向 FragmentTransaction
添加改变的顺序是无关紧要的,除了:
commit()
。如果,在执行了一个移除碎片的事务时没有调用addToBackStack()
,那么,当此次事务被提交时,那个碎片就被销毁了,用户也不能向后浏览到它。但是,当移除一个碎片时调用了addToBackStack()
,则那个碎片就被停止了,并在用户向后浏览时被重新恢复。
提示:对于每次碎碎片事务,在它被提交前,通过调用setTransition()
方法你可以申请一个事务动画。
调用commit()
时并不会立刻执行事务。相反,它被调度到活动UI线程(主线程)上,主线程会尽快运行它。然而,如有需要,你可以从你的UI线程里调用
executePendingTransactions()
来立刻执行由commit()
提交的事务。通常没有必要这么做,除非此事务依赖于其他线程的工作。
警告:你只能在活动保存它的状态(当用户离开活动)之前使用commit()
提交事务。如果试图在此之后提交,将会抛出一个异常。这是因为,如果活动在需要被恢复时,在保存状态之后的提交都被丢失了。 对于在不出现问题的情况丢弃提交,可以使用commitAllowingStateLoss()
。
尽管碎片可以被实现为一个独立于活动的对象,并能被嵌在多个活动内部,但是,碎片的一个特定实例被直接绑定到包含它的活动上。
具体来说,碎片可通过getActivity()
方法访问活动实例,并很容易地执行诸如在活动布局里查找视图的工作。
View listView = getActivity().findViewById(R.id.list);
同样,
使用findFragmentById()
或findFragmentByTag() 方法
,活动可以通过从FragmentManager
获取一个
Fragment
引用,调用其内部的方法。例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
在某些情况下,你可能需要碎片与活动分享事件。一个可以做到这一点的好办法是,在碎片声明一个回调接口,并在宿主活动内来实现它。当活动经由该接口收到回
调时,它可以根据需要与布局内的其他碎片分享信息。
例如,假设一个应用的活动里有两个碎片,一个用来显示文章列表(碎片A),另一个用来展示文章内容(碎片B),那么,当一个列表项被选中时,碎片A必须告诉活动,目的是活动能够告诉碎片B来显示文章内容。这种情况下,在碎片A中声明OnArticleSelectedListener
接口:
public static class FragmentA extends ListFragment { ... // Container Activity must implement this interface public interface OnArticleSelectedListener { public void onArticleSelected(Uri articleUri); } ... }
然后,宿主活动实现OnArticleSelectedListener接口,并重写
onArticleSelected()
方法来通知碎片B。为确保宿主活动实现这个接口,碎片A的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"); } } ... }
如果活动没有实现此接口,那么碎片会抛出ClassCastException
异常。一旦成功,mListener
成员就持有活动的OnArticleSelectedListener
实现的引用,目的是为了碎片A通过调用由OnArticleSelectedListener
接口定义的方法能够与活动分享事件。例如,如果碎片A是ListFragment
的一个扩展,每次用点击列表项时,系统就会碎片里调用onListItemClick()
,然后它又调用onArticleSelected()
方法来与活动分享事件:
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,活动(或其他碎片)使用它来从应用的内容提供器(ContentProvider
)中取出文章。
更多关于使用内容提供器的信息参考Content Providers文档。
碎片可以为活动的Options Menu(和Action Bar)贡献菜单项,通过实现onCreateOptionsMenu()
方法。然而,为使该方法接收到调用,你必须在onCreate()
期间
调用 setHasOptionsMenu()
,以此来预示碎片愿意向选项菜单添加项目(否则,碎片将不会收到onCreateOptionsMenu()
调用)。
任何从碎片内向选项菜单添加的项目都被附加在已有菜单项的后面。当一个菜单项被选中时,碎片也可以收到onOptionsItemSelected()
回调。
注意:尽管碎片可以收到每一个由它添加的菜单项的选中回调,但是,当用户选择一个菜单项时,活动首先收到各自的回调。如果活动的单项选中回调实现没有处理被选中的菜单项,那么该事件就被传递给碎片的回调。对于选项菜单和内容菜单这都是真是。
更多关于菜单的信息,详见Menus 和Action Bar 开发者指南。
管理碎片的生命周期很像管理活动的生命周期。和活动一样,碎片有三个状态:
恢复(Resumed)
碎片在运行的活动内可见。
暂停(Paused)
其他的活动位于前景并获得焦点,但是碎片处在的活动依然可见(前景活动部分透明,或没有完全覆盖住屏幕)。
停止(Stopped)
碎片为不可见。要么是宿主活动被停止了,要么就是碎片从活动里被移走而被添加到后退堆栈里。
被停止的碎片依然是存活的(所有状态和成员信息被系统保存了)。然而,如果活动被取消了,碎片也将被取消并不再对用户可见。
仍像活动一样,在活动进程被取消时可以使用Bundle
保存碎片的状态,并在活动重新启动时恢复它们。你可以在碎片的onSaveInstanceState()
回调期间保存碎片状态,并在onCreate()
,onCreateView()
,或onActivityCreated() 中一个里面恢复状态。更多关于保存状态的信息参考Activities文档。
生命周期在活动和碎片二者间最显著的不同是,它们是如何被保存在各自的后退堆栈内的。默认情况下,当活动被停止时,它被放置在由系统管理的活动后退堆栈中(目的为了用户可以通过"Back"按钮向后浏览它)。然而,只有当你在移除碎片的事务期间通过调用addToBackStack()
方法来明确地要求该碎片被保存时,碎片才会被放置在由宿主活动管理的后退堆栈里。
同样,管理碎片的生命周期非常类似于管理活动的生命周期。因此,管理活动的生命周期的做法同样适用于碎片。不过,你还需要明白的是活动的生命是如何影响碎片的生命。
宿主的生命周期直接影响着碎片的生命周期,以至于活动的每个生命周期回调导致了一个类似的碎片生命周期回调。例如,当活动接收onPause()回调时,活动内的每个碎片都接收
onPause()
回到。
onAttach()
当碎片与活动关联是被调用(活动被传递到这里)。
onCreateView()
被调用来创建与碎片有关联的视图层级。
onActivityCreated()
当活动的onCreate()
方法返回时调用。
onDestroyView()
当与碎片有关联的视图层级被移除时调用。
onDetach()
当碎片从活动中被取消关联时调用。
随着受宿主活动生命周期的影响,碎片的生命周期流程如图(3)所示。
在此图中,可以看到活动的每个连续状态是如何决定碎片可以接收哪些回调方法。例如,当活动收到它的onCreate()
回调时,活动内的碎片不会收到超出onActivityCreated()
的回调方法。
一旦活动到了恢复状态,你就可以自由地向活动里添加和移除碎片了。因此,仅当活动处于恢复状态时,碎片的生命周期才能独立地改变。
然而,当活动离开恢复状态时,碎片再次被活动推向它的生命周期。
为了把上述讨论的内容集中在一起,这有一活动使用两个碎片来创建一个双面板布局的实例。下面的活动包括一个碎片来显示莎士比亚剧本书名列表,另一个碎片来显示剧本摘要,当从列表内选择一个书名时。它同时展示了如何基于屏幕配置来提供不同的碎片配置。
提示: 这个类的全部源代码可在FragmentLayout.java
获得。
在 onCreate()
期间,主活动按通常方式应用了一个布局:
@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>
使用这个布局,活动一将其加载完后,系统就会实例化TitlesFragment
(它列出了剧本书名),而FrameLayout
(显示剧本摘要的碎片出现在这里)占用屏幕右侧的空间,但是在起初时为空的。正如在下面将要看到的一样,直到用户从列表中选择了一项时,一个碎片才被放置进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
碎片,这意味着,当设备是纵向时,仅剧本的书名可见。所以,当用户点击书名列表中的一项时(设备纵向),应用将启动一个新的活动来显示剧本摘要,而不是加载第二个碎片。
接下来,你将看到这如何在碎片类内被实现的。第一个是TitlesFragment
类,它显示了剧本的书名列表。这个碎片类扩展了ListFragment
,并依赖它来处理列表视图的绝大多数工作。
随着对这段代码的检查,注意到当用户点击列表项时这里有两个可能的行为:
取决于两个布局中哪一个是活动的,它可以在同一活动内创建一个新的碎片来显示细节内容(添加碎片到FrameLayout
),或者是启动一个新的活动(碎片被显示在这里)。
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); } } }
第二个碎片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
activity来显示该列表项的内容。
这里是DetailsActivity
,当屏幕为纵向时,它简单地嵌入DetailsFragment
碎片来显示被选剧本的简介:
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(); } } }
注意,当屏幕为横向时,该活动结束其自己,目的为主活动能够接管并沿
TitlesFragment的
旁边显示DetailsFragment
。这是可以发生的,假如用户开始DetailsActivity
时屏幕为纵向的,但然后又旋转到横向(它重新启动当前的活动)。
更多使用碎片的例子(及这个例子的完整代码),参考ApiDemos(可从Samples SDK component下载获得)里的实例代码。
2012年5月4日,毕