一个碎片代表了活动中的UI片段。你可以在一个活动中通过联合使用多个碎片来创建一个多面板的UI,并且在不同的活动中可以多次使用同一碎片。可以把碎片想象成活动的一个模块,它有自己的生命周期,会收到自己的输入事件,而且在活动运行过程中你可以动态增减碎片(有点像一个“迷你活动”,你可以在不同的活动中使用这个“迷你活动”)。
![]()
~将一个碎片pop出返回栈,通过popBackStack()(模拟了一个按下返回键的行为)。
碎片必须要嵌入一个活动中来使用,且碎片的生命周期明显是和它所在活动的生命周期有很大关联的。比如,当活动被pause的时候,活动里的所有碎片也会paused,活动被销毁的时候,碎片也会被销毁。不过,在活动的运行期间(指活动的“可见可交互"期间),你也可以对碎片进行独立的操作,比如添加一个碎片,或者销毁一个碎片,也就是说,活动被销毁则碎片一定被销毁,但是活动在运行时,碎片同样可能被销毁。当你执行这样的一个碎片“事务”(就比如前面的添加删除碎片)时,你同样也可以把碎片加入到返回栈中,返回栈由活动来管理,返回栈中的每一项都是一次碎片事务的记录。把碎片加入到返回栈,可以实现通过按返回键来回退碎片事务,比如你删了一个碎片然后后悔了,就可以按返回键来恢复。
当你把一个碎片作为活动布局的一部分时,在视图等级结构中碎片会挂在一个ViewGroup下面,并且每一个碎片可以定义自己的视图。把碎片插入到活动中的方法一是在活动的布局文件中声明一个<fragment>元素,二是在应用的代码中把它加到一个ViewGroup下。然而,碎片并不是一定要作为活动布局的一部分而存在,你可以使用一个没有UI的碎片,把它作为活动的一个隐形工作者。
这篇文档讲述如何用碎片来构建你的应用,包括把碎片加入到返回栈时碎片如何保留自己的状态,以及碎片与活动间的通信,以及碎片和同活动下的其他碎片间的通信,还有碎片对活动Action bar的好处,等等。
设计理念:
在API11即Android3.0的时候引入了碎片的概念,初衷是为大屏幕构建动态的灵活的UI,比如平板。因为平板的尺寸比手机大了不少,提供给UI控件的活动空间也就更多。碎片可以在不需要管理视图等级树的情况下实现动态的UI。通过把活动的UI分割成一个个的碎片,你可以在运行时动态地修改活动的视觉效果并且可以通过活动的返回栈来保留这些更改。
比如,一个新闻客户端可以在左边用一个碎片来展示文章标题列表,然后在右边展示你选中的某篇文章。两个碎片处在同一活动下,互相毗邻,并且每个碎片各自管理它自己的生命周期以及处理自己的输入事件。所以,我们没有使用一个单独的活动来选择文章,另一个活动具体显示文章内容的这种做法,用户可以在同意活动下选择并阅读文章,如下图中tablet所示。
要把每个碎片作为一个模块化的、可重用的活动组件来设计。因为每个碎片拥有自己的布局自己的行为以及自己的生命周期回调,所以你完全可以在多个活动中使用重复使用某碎片,所以在设计碎片时要考虑到可重用的特性,且尽量避免在一个碎片中直接对另一个碎片进行操作,这一点很重要,因为一个模块化的碎片允许你针对不同的屏幕大小来改变碎片之间的互联关系。如果想让你设计的应用可以同时兼容手机和平板,你可以在不同的布局配置下使用同样的碎片,以此来给优化你的不同屏幕下的,用户体验。比如,在手机上,如果多个碎片难以同屏幕共存的话,我们会把碎片放到不同的活动中,提供一个单窗口的UI。

两种情况,一目了然。
再拿新闻客户端来举例,在平板上运行的时候,我们的应用完全可以把两个碎片放在同一个活动下,比如Activity A。然而在手机上运行的时候,俩碎片还放同一活动的话就放不下了,此时Activity A仅放一个碎片,即文章列表的碎片,然后当用户点了其中某一项,就启动Activity B,其中就包含着一个显示文章内容的碎片来让用户阅读。这样下来,我们的应用就可以通过以不同的关联方式重用碎片,实现了兼容平板和手机。
想了解更多的关于使用不同碎片关联方式来设计兼容不同屏幕的应用,就看看Supporting Tablets and Handsets这篇文档吧。
创建碎片:
想创建碎片,需要继承Fragment类或者它的子类。Fragment类的一些代码和Activity是很相似的。比如它有着onCreate(),onStart(),onPause(),onStop()这些回调方法。事实上,如果你正想着把你原来用Activity设计的应用转换成用碎片来实现的话,直接把Activity中的代码拷贝到其对应的Fragment回调方法下就好了。
通常,至少要实现一下几个回调方法:
onCreate()
系统在创建碎片的时候调用这个方法。在这个方法里,你应该把一些碎片相关联的、并且你希望在碎片被Pause或Stop之后恢复时要保留下来的组件进行一下初始化。
onCreateView()
系统在碎片初次画它的UI时调用这个方法。要画UI就需要在这个方法里返回一个View。这个View就是你碎片布局中的根节点。如果碎片压根没有UI,返回null就好。
onPause()
这个方法是一个提示,系统会在用户要离开此碎片时调用这个放法,提示你“用户不要你这个碎片了”,(当然,暂时不要了并不意味着就会被销毁)。这个方法,你应该用来提交一些持久性的改变。
一般来说,碎片都应该至少实现以上三个回调方法,当然还有其他不少回调方法在下面的章节中我们会讨论。
下面是一些Fragment的子类,你可以直接继承它们来实现你的碎片:
DialogFragment
显示一个浮动对话框。使用这个类来创建对话框是个好主意,相比于在活动中使用dialog各种方法来创建对话框来说,这个碎片的好处在于它可以加到活动的返回栈中,然后用户之后就可以回退到这个对话框。
ListFragment
显示一个由adapter(比如SimpleCursorAdapter)管理的项目列表,有点像ListActivity。提供了几个方法来管理这个列表,比如onListItemClick()来处理点击事件。
PreferenceFragment
以列表形式显示一个Preference对象的等级结构。类似PreferenceActivity。当给你的应用设计“设置”页面的时候可以考虑用这个碎片。
加入UI:
碎片通常被用作活动的UI的一部分,来构成活动的布局。
可以通过实现onCreateView()方法来给碎片设定布局,当碎片需要画布局的时候系统会自动调用onCreateView()方法。实现此方法,必须返回一个View,作为这个碎片的布局中的根节点。
注意:如果你的碎片继承的是ListFragment,它默认的onCreateView()实现会返回一个ListView,你就不用实现了。
那么怎么在onCreateView()中返回布局呢,可以将一个写好的布局文件填充进来。正好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);
}
}
container参数,就是碎片的父元素,也就是这个碎片插入到的那个活动布局中的某个ViewGroup。而savedInstanceState参数则是一个Bundle,在碎片resume的时候,这个Bundle提供了一些之前保留下来的信息,下面的处理碎片生命周期的章节会详细讨论这个状态的保留与恢复。
inflate()方法接收三个参数:
~要填充的布局资源ID
~一个ViewGroup作为填充好的布局的父元素。传入container很重要,它可以让填充好的布局的根节点的一些layout参数生效,因为这个layout参数的生效都是需要父元素的,所以传入的container也就是那个ViewGroup就是起到这个作用。
~一个布尔值,指示了这个填充布局在填充过程中是否要绑定到上面说的ViewGroup上。在示例中我们传的是false,因为系统本来就是要把这个布局插入到ViewGroup下,如果传true的话会搞出一个多余的ViewGroup。
我们已经学会了如何创建一个带布局的碎片。接下来,就看看如何把碎片加入到活动中吧。
将一个碎片加入到活动中:
通常,碎片是作为宿主活动的UI的一部分而存在的。有两种方法把碎片加入到活动布局中:
1.在布局文件中声明碎片。
这种方式下,你可以像对待普通的View那样,给碎片加一些layout属性。比如,下面是包含两个碎片的活动布局:
<?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类来实例化这个布局。
当系统创建这个布局的时候,会把布局中每个碎片都实例化且调用他们的onCreateView()方法,通过方法的返回值来获取碎片布局。然后把这个返回的布局直接插入到活动布局中<fragment>元素对应的位置上。
注意:每个碎片都需要一个标识符,当活动restart的时候,系统可以根据这个标识符来恢复碎片。而且你也可以用这个标识符来获取碎片,然后执行一些碎片事务,比如删掉它。有三种方式可以给碎片提供标识符:
通过android:id属性直接设定ID
通过android:tag属性设定一个字符串标识符。
如果上面两种你都没用,系统会默认使用碎片父元素的ID。
2.在代码中把碎片加入到一个ViewGroup下。
活动运行期间的任意时刻,你都可以给活动布局加入碎片。只需要提供一个ViewGroup作为碎片的容器或者父元素。
想要在活动内部执行一个碎片事务(比如添加or删除or替换碎片),你需要用到FragmentTransaction这个类的方法。而如何在活动中获取到这个FragmentTransactio的实例呢,下面是例子:
FragmentManager fragmentManager =getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
通过这两步的操作,然后就可以使用add()方法加入碎片,并指明加入哪个碎片,以及往哪里加入碎片。例子:
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
add()方法第一个参数,是碎片的容器,或者父元素,通过资源ID来给出。第二个参数就是你要加的碎片。
在碎片事务中做出改变后,需要调用commit()来使这个改变生效。
添加一个不带UI的碎片:
上面的例子都是要把一个带布局的碎片加入到活动中,作为UI的一部分。然后,你同样可以使用不带UI的碎片来为活动执行一些后台的操作。
添加不带UI的碎片需要在活动内部调用add(Fragment, String)方法(字符串参数是一个标识符,在没有资源ID的情况下用字符串作为tag很方便)。这个方法可以把碎片添加到活动,但是并没有插入到活动的视图等级中的某一个节点,且系统也不会去调用碎片的onCreateView()。所以这个方式下你就不用实现onCreateView了。
给碎片提供字符串tag来作为标识符这个方法,同样适用于带UI的碎片。只不过对于没有UI的碎片来说,这是唯一的提供标识符的方法了(带UI的可以用ID)。添加完之后如果你想获取碎片,调用findFragmentByTag()就好了。
不带UI,在后台运行的碎片示例可以看看FragmentRetainInstance.java
管理碎片:
管理活动中的碎片需要用到FragmentManager类。在活动内部调用getFragmentManager()就可以获取它的实例。
你可以用这个manager来实现这些功能:
~获取活动中的碎片,通过findFragmentById()(带UI的碎片才可以哦)或者findFragmentByTag()(带不带UI的碎片都行)。
~将一个碎片pop出返回栈,通过popBackStack()(模拟了一个按下返回键的行为)。
~为返回栈的变化注册一个监听器,addOnBackStackChangedListener()。
更多关于这些方法的信息尽在FragmentManager的reference文档。
执行碎片事务:
在活动中使用碎片的一大特色就是可以增删替换碎片,或者对碎片进行一些别的操作,作为对用户的响应。每次你提交一些变动的时候,都看作是一次事务。执行事务需要用到FragmentTransaction。同时你也可以把事务存到活动的返回栈上,让用户可以实现撤销改变(类似于你在不同活动间按返回键回退)。
获取FragmentTransaction方法:
FragmentManager fragmentManager =getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每个事务代表了你想要同时进行的一系列改动。你可以通过调用add(),remove(),replace()来实现你的这一系列变动,完了一定要记着调用commit()来把这个变动应用到活动上。
在调用commit()之前,你可以试着调一下addToBackStack(),这个方法可以把你的事务添加到返回栈中。返回栈由活动来管理。这样就可以实现让用户按返回键来撤销你的这一系列变动,把状态恢复到变动之前的样子。
比如,这个例子展示了如何用一个碎片替换另一个,并且把这个事务存到返回栈里:
// 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下的碎片,或者说替换掉了这个container下的所有子视图。而通过调用addToBackStack(),这个替换事务会被保存到返回栈中。然后用户可以通过返回键把那个被替换掉的碎片恢复回来。
如果你在事务中做出了多个改动(比如add()了好几次)并且调了addToBackStack(),你在commit()之前所做的所有改动都会被保存到返回栈中,然后用户可以按下返回键撤销这一系列的改变。
add()replace()remove的调用顺序一般都无关紧要,但是要注意以下几点:
~commit()必须在最后调用
~如果你要给同一容器加入多个碎片,那么你加入碎片的顺序就决定了这些碎片在视图等级结构中的出现顺序。
如果在删除某碎片的时候你没有把这个事务加入到返回栈,那么commit()之后这个碎片会直接被销毁掉,用户无法通过返回键恢复它。如果你调了addToBackStack()把删除事务加入了返回栈,那么这个碎片不会被销毁,而是被stop,然后用户可以用返回键恢复它。
提示:对每一次碎片事务,你都可以给它们设定一个转换动画,需要在commit之前调用setTransition()方法。
调用commit()并不会使事务立即生效,而是把这个事务排到UI线程中等待执行。必要时,可以在UI线程中调用executePendingTransactions()立即执行一个已commit()的事务。一般没这个必要,除非有其他的线程在等待你的这个事务的执行结果。
注意:事务的提交一定要先于活动开始保存自己状态的时间点,也就是当用户要离开活动的时候系统会把活动的一些状态保留下来以便将来恢复,那么你的事务提交一定要比这个保留状态的行为更早才可以。否则会报错,因为这个事务的执行状态没有被保留下来,之后如果用户想要回到这个活动的话可能会导致前后不一致的状态,不过如果你觉得保留不保留无所谓的话,那就调用commitAllowingStateLoss()。
碎片与活动通信:
虽然碎片是作为一个独立于活动的对象来实现的,并且可以被重用,但是对于一个特定的某碎片实例来说,它是直接和宿主活动绑定在一起的。
碎片可以通过getActivity()来直接访问活动实例,然后可以轻易执行一些类似findviewbyid之类的操作。
View listView =getActivity().findViewById(R.id.list);
类似地,活动也可以调碎片的方法,通过向FragmentManager索取一个碎片实例,即调用FragmentManager的findFragmentById()或findFragmentByTag()。例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
为活动创建事件回调:
某些情况下,你可能需要碎片和活动共享一些事件。这时就可以在碎片内部创建活动回调接口,然后让活动去具体实现它。当活动通过这个接口收到调用的时候,就可以在必要的情况下和同活动中的其他碎片共享一些信息了。
例如,某新闻客户端在一个活动里有两个碎片,碎片A展示了文章的列表,碎片B展示具体某篇文章的内容。那么当用户点击了A中的某篇文章的时候,A需要让活动知道具体点击的是哪一篇,然后由活动来告诉B去显示这篇文章的内容。下面的代码中,OnArticleSelectedListener接口在A碎片中定义:
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
接着,碎片A的宿主活动需要实现OnArticleSelectedListener接口并重写onArticleSelected()方法,根据A中的事件动态地提醒B。为了保证宿主活动实现这个接口。碎片A的onAttach()方法中可以将传入的Activity强转为OnArticleSelectedListener来实例化这个接口,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");
}
}
...
}
如果活动没有实现这个接口,就会报出ClassCastException。而如果强转成功的话,mListener这个成员变量就会拥有随时访问活动对OnArticleSelectedListener的实现的能力。这样,碎片A就可以调用接口中的方法来实现与活动共享事件。比如,如果碎片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,然后活动(或其他碎片)根据这个ID去应用的ContentProvider查询到对应的文章。
关于内容提供者的使用,尽在Content Provider那篇文档。
向Action Bar中加入条目
你的碎片可以通过实现onCreateOptionsMenu()向活动的Options Menu(进而就是Action Bar)中加项目。为了保证收到调用,在onCreate()方法中需要调一下setHasOptionsMenu(),指明碎片要向OptionsMenu中加入项目。否则的话,是调不到碎片的onCreateOptionsMenu()的。
来自碎片的OptionsMenu条目会加到现有条目之后,当菜单中某项被点击到的时候碎片也会收到onOptionsItemSelected()调用。
你也可以在把碎片布局中的某个View注册,来提供一个context menu,通过调用registerForContextMenu()。当用户打开context menu的时候,碎片会收到onCreateContextMenu()调用,而当用户点击了某项目,会收到onContextItemSelected()调用。
注意:对于每一个由碎片加进去的条目,用户点击的时候碎片会收到onItemSelected调用。但是要注意,活动是第一个收到这个调用的,如果活动的onItemSelected中没有处理这个点击事件,然后这个事件才会被传到碎片。OptionsMenu和contextmenu都是这样的情况。
管理碎片生命周期:
管理碎片生命周期和管理活动的生命周期是很相像的。类似活动,碎片也有三种存在状态:
Resumed
碎片在当前活动中可见。
Paused
另一个活动挡在了前台,并且拥有用户焦点,但是碎片所在的活动依然可见,也就是前台的活动并没有完全挡住后面。
Stopped
碎片不可见。要么是宿主活动被stop要么是碎片被removed掉并且加入了返回栈。被stop的活动依旧是存活着的,也就是碎片的一些状态信息都还保留在系统中。但是用户是看不到这个碎片了,并且如果之后宿主活动被销毁的话碎片也会跟着被销毁。
同样,类似活动,你可以使用一个Bundle来保存碎片的状态,以防活动所在进程被杀掉而之后重建时你需要恢复一些状态。你可以在碎片的onSaveInstanceState()方法里保存状态然后在onCreate()或onCreateView()或者onActivityCreated()里恢复状态。Activity那篇文档有更多的关于保存状态的讨论。
碎片和活动的生命周期最大的区别在于它们加入到返回栈的方式。活动被stop时,默认加入的那个返回栈是由系统来管理的,栈中每一个条目都是一个个活动,然后用户可以通过返回键回到这个活动。而碎片,只有在一个remove的事务中,通过addToBackStack()来要求把它加入返回栈的时候才会被加入,并且加入的那个返回栈是由宿主活动来管理的。
除此之外,管理碎片的生命周期和管理活动的生命周期基本没什么区别了,所以在活动那篇文档中讨论的关于生命周期的管理也同样适用于碎片。另外要理解活动的生命周期是如何影响到碎片的。
注意:如果在碎片的内部你需要一个Context对象,可以调用getActivity()。然而,调用这个方法时需要小心:一定要在碎片被绑定到某活动的时候才可以调用。如果碎片并没有被绑定到活动,或者本来绑定了但是被销毁解绑了,这个时候调用getActivity()会返回null。
与活动的生命周期协同工作:
碎片的宿主活动的生命周期会直接影响到碎片自己的生命周期,所以每一个活动生命周期的回调都会导致一个对应的碎片生命周期回调。比如,当活动收到onPause()调用时,活动中的每个碎片都会调到onPause()。
碎片有一些不同于活动的额外的回调,用来处理一些碎片独有的任务,比如建立或者销毁碎片的UI。这些额外的回调有:
onAttach()
当碎片被加到某活动中的时候调用(宿主活动会作为参数传入)。
onCreateView()
创建碎片的UI及视图等级结构时调用。
onActivityCreated()
当宿主活动的onCreate()方法返回的时候调用。
onDestroyView()
碎片自己的视图等级结构被销毁的时候调用。
onDetach()
当碎片从活动中被剥离关系的时候调用。

上面的Figure3展示了碎片的生命周期,以及它如何被活动的生命周期影响。图中可以看到,活动对应的每一个状态决定了碎片可能会收到哪些回调。比如,当活动被调到onCreate()的时候,其中的碎片不可能被调到onActivityCreated()以下的方法。
而活动如果变为了resumed状态,这是你就可以随意的增删碎片。并且也只有在这个状态下,碎片的生命周期可以独立的自由的变化,也就是当活动处在resumed状态的时候,碎片的任何生命周期回调方法都可能被执行到。
如果活动不再处于resumed状态,那碎片的生命周期又陷入的活动的控制中。
示例:
总体把这篇文档的东西都应用一下,下面这个例子中我们用两个碎片创建了一个双面板的布局。其中一个碎片展示莎士比亚的剧作标题列表,另外一个碎片展示当点击列表中某项的时候对应的剧作简介。而且这个例子也展示了如何根据系统屏幕的大小来提供不同的碎片配置。
注意:完整源码在FragmentLayout.java中。
主活动很简单,设了一个布局而已:
@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占用了屏幕的右半边空间,但是初始状态是空白的(将来会把作品简介加入到这个layout中)。下面你会看到,知道用户点击了左边列表中的某项的时候才会把碎片加入到这个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类依靠这个父类来处理多数的list view工作。
看代码的时候要注意,当用户点击到某一项的时候可能会有两种响应,取决于上面提到的两种布局情况到底是哪种生效:可能会在同一活动下加入一个新的碎片来展示作品概要(即在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();
if (index == 0) {
ft.replace(R.id.details, details);
} else {
ft.replace(R.id.a_item, 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展示作品内容。
下面就是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();
}
}
}
注意到这个活动在屏幕为水平方向的情况会直接finish,然后让主活动接手来显示具体内容。这种情况会发生在用户在竖屏情况下启动了DetailsActivity然后又切到了横屏。
更多碎片的例子看ApiDemos。
posted on
浙公网安备 33010602011771号