API Guides:Fragment

一个fragment呈现activity中的一个行为或者一部分用户界面,你可以在一个activity中嵌套多个fragment,也可以在多个activity中复用一个fragment,你可以把fragment想成是activity的一个组合模块,它有自己的生命周期,接受自己的输入事件,并且你可以在activity运行时进行fragment的添加、删除操作。


fragment必须内嵌在一个activity中,并且它的生命周期被宿主activity的生命周期直接影响。比如,当这个activity被paused,那么它里面所有的fragment都会被paused,当这个activity被destroyed,那么它里面所有的fragment也都会被destroyed。但是,当activity处于running状态时,你可以自主地操作fragment,比如添加、删除fragment。当你执行一个fragment事务,你可以把fragment添加到一个宿主activity的backstack(返回栈)中——每个返回栈记录了被触发的fragment事务。返回栈允许用户通过点击back按键倒退fragment事务。

当你添加一个fragment作为你的activity中的一部分时,它存活于activity的视图层级关系中的viewgroup里面并且可以定义自己的view布局。你可以通过在activity的layout布局文件中声明一个fragment(作为<fragment>元素)来插入一个fragment,也可以通过代码在程序中把它添加到已存在的一个viewgroup中。然而,一个fragment并不被要求一定要作为一个activity的layout文件中的一部分,你也可以使用一个fragment作为activity一个不可见的部分。

本文档主要描述如何使用fragment来创建你的application,包括当一个fragment被添加到activity的返回栈中怎么可以维持他们的状态、与activity和activity中的其他fragment共享事件、为activity中action bar做出贡献,还有一些其他的内容。

设计原理

Android在version3.0里第一次介绍了fragment,主要是为了更多的支持大屏幕的动态灵活的UI设计,比如说在平板上。因为平板的屏幕比一般手机的屏幕大,拥有更多的空间来组合交换UI组件。fragment允许你不需要去管理复杂的view层级变化。通过把activity布局分成多个fragment,你可以在runningtime修改activity的外观并且在activity管理的返回栈里保存变化。

例如,一个程序可以在左边显示一个商品表而在右边显示这个商品的详情——两个fragment显示在一个activity中,每个fragment都有自己的生命周期回调方法并处理自己的输入事件。如此,用户在一个activity里选择一个商品并查看商品详情,而不是在一个activity选择商品却在另外一个activity中查看商品详情。


在你的application中fragment应该是一个标准且可重用的组件。那是因为fragment使用生命周期回调方法定义自己的布局和行为,你可以在多个activity中包含一个fragment。fragment可以提高不同屏幕尺寸的用户体验效果这点很重要。例如,当屏幕够大时你可以在一个activity中包含多个fragment,否则你可以通过fragment分裂成多个activity区显示。

例如--继续以上面那个图的应用为例--这个应用能嵌入两个fragment在Activity A,当运行在一个特大屏幕时(如在tablet 平板),然而,当运行在一个普通大小的屏幕时,没有足够的空间放下两个fragment,所以Activity A只包含商品列表的fragment,当用户选择一个商品时,它start 显示商品详情的Activity B,因而,应用支持两种设计模式。

创建Fragment

要创建一个fragment,你必须创建一个继承Fragment的子类。这个fragment类在代码上很像activity。包含一些和activity很像的回调方法,比如onCreate()、onStart()、onPause()和onStop()。实际上,假如你正在使用fragment来重写一个以前的application,你可能仅仅需要把activity回调方法中的代码复制到fragment的回调方法中去。

通常你至少需要实现下面这几个生命周期回调方法:

onCreate():当创建一个fragment时,系统回调这个方法。在这个方法中,你应该初始化这个fragment的基础组件——当这个fragment暂停或者停止时你想保留的东西(初始化上次离开这个fragment时所保存的一些状态)。

onCreateView():当这个fragment第一次绘制它的UI时,系统回调这个方法。为了给你的fragment绘制一个UI,你必须从这个方法里返回一个View作为你的fragment布局的rootView。假如这个fragment不提供一个UI的话就可以返回null。

onPause():当用户离开这个fragment时,系统回调这个方法,作为用户正在离开这个fragment的第一个 提示(尽管这并不意味着这个fragment正在被销毁)。在这里你通常需要保存一下状态的变化(因为用户可能不会再回来了)。

大多数application至少需要为每个fragment实现上面三个回调方法,但是有几个其他的回调方法,你可能也要实现用来处理fragment生命周期的不同阶段。其它全部的生命周期回调方法在稍后讨论。


还有几个Fragment类的子类你可能想要继承,来代替继承Fragment类:

DialogFragment

显示一个悬浮的对话框。相对于使用Activity类中dialog帮助方法,使用这个类来创建一个对话框是一个更好的选择。因为这样你可以把一个对话框收编到这个activity管理的fragment返回栈中去,允许用户返回一个消失了的fragment

ListFragment

显示一个被adapter(比如SimpleCursorAdapter)管理的列表,类似于ListActivity。它提供了一些方法来管理一个列表视图。比如onListItemClick()回调方法用来处理点击事件。

PreferenceFragment

显示一个Preference对象层次结构作为列表。类似于PreferenceActivity。当你要为你的application创建一个设置界面时是很有用的。


添加一个用户界面

fragment通常被当做activity的用户界面的一部分来使用,并且可以导入自己的布局到activity中。

为了给一个fragment提供一个布局,你必须实现onCreateView()回调方法。

注意:假如你的fragment是ListFragment的一个子类,onCreateView()默认是返回一个ListView,所以你可以不实现这个方法。

为了从onCreateView()方法返回一个布局,你可以inflate一个使用XML定义的layout资源文件。为了帮助你这样做,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是父ViewGroup(你的fragment将被内嵌在这里面)。参数savedInstanceState是一个bundle,假如这个fragment是被恢复的,它提供前面那个fragment实例的数据。

这个inflate()方法有三个参数:

  • 你想要加载的布局文件的资源ID。
  • 这个ViewGroup是你即将要加载进来的布局文件的父视图;传递这个container是很重要的,为了让系统应用布局参数于即将要加载进来的布局的根view,指定到将要被内嵌到的view。
  • 一个Boolean变量表明这个即将加载的布局在加载过程中是否依附于这个ViewGroup(既然这样,那么就设置为false,因为系统已经把inflated layout插入到container中了——传递true的的话在最终布局将会创建一个多余的view group)。

现在,你已经知道如何通过提供layout文件来创建一个fragment了。下一步,你需要把fragment添加到activity中。

添加一个fragment到activity

通常,fragment作为宿主activity的UI界面的一部分,作为activity整个视图层级结构的一部分内嵌进去。你可以通过两种方式来给activity的布局添加fragment:

  • 在activity的布局文件里面声明fragment。

在这种情况下,你可以就像给一个View指定属性一样给fragment指定布局属性。例如,下面是一个包含两个fragment的activity布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

<fragment>标签中的属性android:name属性制定了这个布局将要实例化的那个Fragment类。

当系统创建这个activity布局时,它将会实例化layout中指定的每个fragment和为每个fragment调用onCreateView()方法。系统将会用onCreateView()方法返回的View来替代<fragment>元素。

注意:每个fragment必须要求有唯一的标识符以至于当一个activity是restarted时系统可以用来恢复这个fragment(和你可以用来捕获fragment执行的事务,比如移除操作)。有三种方式可以为一个fragment提供一个ID:

    • 给android:id提供一个唯一的ID。
    • 给android:tag提供一个唯一的string。
    • 假如上面两种方式你都不用,系统会使用container视图的ID号来标识。

  • 或者,以编程的方式来向一个已存在的ViewGroup中添加一个fragment。

当你的activity处于running的任何时候,你都为你的activity layout添加fragment。你只需要简单指定一下这个fragment放置于哪个ViewGroup中就行了。

为了在你的activity中处理fragment事务(比如添加、移除、替换),你必须使用FragmentTransaction。在你的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()方法的第一个参数是一个ViewGroup(这个fragment应该放置的地方),通过资源ID来指定。第二个参数是要添加的fragment。

一旦你通过FragmentTransaction做出了改变,你必须调用commit()方法来使得改变生效。

添加一个没有UI界面的fragment

上面的示例展示了通过添加一个fragment来给你的activity提供一个UI界面。但是,你也可以通过使用一个没有额外的UI界面的fragment来为activity提供一个一个后台行为。

为了添加一个没有UI界面的fragment,通过从activity调用add(Fragment, String)来添加一个fragment (为这个fragment提供一个独一无二的tag标签,而不是一个view ID)。但是,这种方式添加一个fragment,没有与activity layout中的一个View关联起来,它不会接收到一个onCreateView()调用。所以你不需要实现这个方法。

通过提供一个字符串标签tag来添加一个没有UI界面的fragment是不严格的——你也通过提供一个字符串标签tag来添加一个有UI界面的fragment——但如果fragment没有UI界面,然后这个字符串tag就是唯一的方式来定义fragment了。在之后假如你想从activity中获取到这个fragment,你想要使用findFragmentByTag()。

管理Fragment

为了管理在你的activity中的fragment,你想要使用FragmentManager。为了得到它,你需要在activity中调用getFragmentManager()。

你可以通过FragmentManager类做到的一些事情:

  • 通过调用findFragmanagerById()方法和findFragmentByTag()方法来获取这个activity种存在的fragment。
  • 通过调用popBackStack()方法来把fragment从返回栈中推出来(模拟一个用户后退命令)。
  • 通过addOnbackStackChangedListener()方法来注册一个返回栈变化监听器。

执行fragment事务

关于在activity中使用fragment的一个很好功能是添加、移除、替换和执行一些其他的操作,响应用户交互方面的能力。

每次你提交给activity的一系列改变叫做一个事务,你可以使用API FragmentTransaction来执行一个事务。你也可以保存每一个事务到由activity管理的返回栈中,这样你就可以向后导航至fragment之前的变化。

你可以像下面这样通过FragmentManager获取到一个FragmentTransaction:

每一个事务是你想要同时执行的一系列变化。你可以使用比如add()、remove()和replace()这些方法为某个指定的事务设置所有你想执行的改变,然后,为了让这个事务应用到activity中去,你必须调用commit()方法。在你调用commit()方法之前,你可能会想调用addToBackStack()方法来把这个事务添加到事务返回栈中去,这个事务栈由activity管理它允许用户通过按下返回键来回退至一个fragment状态。

例如,下面这个例子就会告诉你怎么使用另外一个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();

在上面这个例子当中,新的fragment替代了容器R.id.fragment_container中的fragment。并且通过调用addToBackStack,把这个替换事务保存至了返回栈中,以至于用户可以通过按下返回键来反向执行这个事务并回退至上一个fragment。

假如添加了多个变化到你的事务中(如add()和remove())并且调用了addToBackStack()方法,在你调用commit()方法前的所有操作是作为一个事务的,当你按下回退键,这些所有的操作都将被反转。

你添加至事务的所有变化的顺序是无关紧要的,除了:

  • 你必须最后调用commit()方法。
  • 如果你向同一个容器中添加多个fragment,那么你添加它们的顺序决定了它们在视图层次中出现的顺序。

当你执行一个移除fragment的事务时,如果你没有调用addToBackStack()方法,那么当事务被提交,那个fragment就会被销毁,并且用户不能返回到那个fragment。

小点对于每个fragment事务,你可以在commit()方法前通过调用setTransition()方法设置一个渐变动画

调用了commit()方法后并不会立即执行。而是一旦主线程能够运行到时,就把这个事务添加到activity的UI线程(主线程)调度表中。假如有必要的话,你可以在你的UI主线程中调用executePendingTransaction()方法来立即执行commit()提交的事务。通常情况下是没有必要这样做的,除非在别的线程工作中对这个事务有依赖。

警告:你只能在activity保存它的状态前使用commit()提交fragment事务。假如你在那个点之后提交的话将会抛出异常。这是因为如果activity需要restore,提交之后activity的state可能会丢失。对于这种丢失activity状态是你可以接受的情况,你就使用commitAllowingStateLoss()方法吧。

与Activity通信

虽然fragment是作为一个独立于activity的对象实现的,并且可以插入到多个activity中,但一个给定的fragment实例化对象是直接被绑定到包含它的activity上的。

特别地,fragment可以通过getActivity()方法来访问到activity实例对象,并且可以轻易的执行比如查找activity layout中的某个view:

View listView = getActivity().findViewById(R.id.list);
同样地,你可以在activity中通过FragmentManager获取一个Fragment的引用来调用fragment类中的方法,例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

创建activity的事件回调方法

在某些情况下,你可能需要fragment与activity共享事件。一个比较好的方式就是:给这个fragment插入一个回调接口,并且要求他的宿主activity实现这个接口。当activity通过这个接口接收到一个回调(事件)时,如果有必要的话,它可以与布局中的其它fragment共享信息。
比如说,如果应用中的一个activity它有两个fragment—— 一个用来用来展示商品列表(fragment A)而另外一个用来展示商品详细信息(fragment B)—— 然而当一个商品条目被点击时,fragment A必须要能够告诉activity以至于activity能够告诉fragment B来展示商品详细信息。在这种情况下,这个OnArticleSelectedListener接口是在fragment A中被声明的:
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应该在onAttch()回调方法(当一个fragment被添加到activity中时系统会调用)中通过传递进来的参数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 A就会抛出一个ClassCastException异常。在成功的情况下,mLstener会持有这个activity实现OnArticleSelectedListener接口的引用,以至于fragment A可以通过调用在OnArticleSelectedListener接口中定义的方法来与activity共享信息。比如,假如fragment A 是ListFragment的一个子类,每次当用户点击一下列表项,系统都会调用fragment A中的onListItem()方法,我们可以在这个方法中调用onArticleSelect()方法来通知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)用这个ID来从应用的内容提供者中获取商品详细信息。

添加items到action bar

你可以在fragment中通过实现onCreateOptionsMenu()方法来给宿主activity提供OptionsMenu(选项菜单)。但是,为了这个方法能够被调用你必须在fragment的onCreate()方法中调用setHasOptionsMenu()方法,来声明这个fragment想要给OptionsMenu添加一个选项( 不这样做的话,fragment的onCreateOptionsMenu()方法是不会被调用的)。
你从fragment中添加的items都会被依附到activity已存在的菜单项中去。当一个菜单项被点击时,fragment也可以接收到回调函数onOptionsItemSelected()的调用。
你也可以通过调用registerForContextMenu()方法将你fragment布局文件中的一个View注册到上下文菜单,当用户打开上下文菜单这个fragment就会接收到一个onCreateContextMenu()的调用,当用户选择了一个上下文菜单项时fragment将收到onContextItemSelceted()的调用。
注意:虽然fragment能够接收到它添加到菜单的每个菜单项的选中回调,但当用户选中一个菜单项时,其实activity才是第一个接收到回调的。如果在activity中菜单选中回调方法里面没有对这个事件进行处理,那么这个事件将会传递到fragment。

处理fragment的生命周期


fragment的生命周期管理和activity的生命周期管理特别地像。就像activity一样,一个fragment可以存在以下三种状态:
Resumed
这个fragment在正在运行的activity中是可见的。
Paused
另一个activity处与前台并且获取了焦点,但是包含这个fragment的activity还可见到(那个前台activity部分透明或者没有完全覆盖整个屏幕)。
Stoped
这个fragment是不可见的。fragment的宿主activity已经停止,或者fragment被activity移除并且被保存到了返回栈中。一个停止的fragment是仍然存活着的,但是,它不再对用户可见,并且随着activity被killed而killed。
像activity一样你也可以用Bundle的保存状态,假如当你的activity被killed并且要被recreated的时候你就需要重新恢复fragment的状态。你可以在fragment的onSaveInstanceState()方法中进行状态保存,然后在onCreate()、onCreateView()或者onActivityCreated()方法中进行状态恢复。
fragment和activity在生命周期上最大的不同是:它们如何被保存在各自的返回栈中。默认情况下,当activity被stopped时,它被存放至系统管理的activities返回栈中(所以用户可以像 Tasks and Back Stack中讨论的一样向后导航)。而fragment是被保存在宿主activity所管理的返回栈中,仅当在执行fragment移除事务时,你显示地调用了addToBackStack()方法才会被保存。
除此之外,fragment的生命周期管理也非常类似于activity的生命周期管理,所以,activity生命周期管理办法同样适用于fragment。虽然如此,但你也需要知道,activity的声明周期是如何影响fragment的生命周期的。

与activity的生命周期协作

宿主activity的生命周期将直接影响fragment的生命周期。正因为如此,每个activity的生命周期回调方法会导致对每个fragment类似的调用。比如,当activity接收到onPause()调用,那么这个activity中的每个fragment也将接收到一个onPause()调用。
fragment还有一些额外的生命周期方法,那是为了执行像构建和销毁fragment UI界面这类与activity独特的交互,这些额外的回调方法是:
onAttch()
当fragment关联到activity上时调用。
onCreateView()
创建于fragment相关联的的视图层次时调用。
onActivityCreated()
当activity的onCreate()方法已经返回时调用。
onDestroyView()
当与fragment相关联的视图层次被移除时调用。
onDetach()
当fragment从activity中解除关联时调用。

图中说明了fragment的生命周期的流动,就像它的宿主activity影响的一样。在这个图中,你可以看到activity每个连续的状态是如何决定fragment可能接收到的回调方法的。比如,当activity已经接收到了onCreate()回调方法的调用,activity中的一个fragment仅仅接收到onActivityCreated()方法的调用。
一旦activity到了resumed状态,你就可以自由的为这个activity添加和移除fragment。因此,只有当activity处于resumed状态时,fragment的生命周期状态才能独立的改变。
但是,当activity离开resumed状态时,fragment再次被宿主activity的生命周期所推动。

案例

为了把本文档所讨论的东西集中到一起,这个activity的例子中使用两个fragment来创建一个 两窗格 布局视图。在下面的这个activity中包含一个用于显示话剧标题列表的fragment和另一个显示被选中的话剧详情。这个案例也演示了对于不同的屏幕配置提供不同的fragment配置。
注意: 这个案例的完整的代码你可以在FragmentLayout.java中找到。
像平常一样在main activity的onCreate()方法中设置一个layout:
@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家在这个fragment_layout.xml文件时,system会尽快实例化这个ListFragment,同时FrameLayout占领屏幕的右侧空间,但开始这里任然是空的。就像你从下面看到的一样,直到用户从列表中选择一项才会有一个fragment被放置在FrameLayout中。
但是,并不是并不是所有的屏幕都宽度足够一边放标题另一边放详情。所以,上面这个layout只适用于横向配置的屏幕,通常保存在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>
这个布局只包含了TitleFragment,意思就是说当屏幕竖屏时只能看到商品列表,所以用户在此配置上点击列表项时应该用一个新的activity来显示商品详情而不是加载第二个fragment。
接下来,你将看到在这个Fragment类中是如何完成工作的。首先是显示商品列表的TitlFragment,这个类继承ListFragment类,并且依赖这个类来处理大部分列表视图工作。
当你阅读这段代码时,注意当用户点击商品列表项时可能会出现两种行为。这主要取决于上面两种layout哪一个被激活了。要么在同个activity中创建并显示一个新的fragment,要么开启一个新的activity。
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);
        }
    }
}
第二个fragment是DetailsFragment展示了你在TitleFragment中选中的商品的详情:
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;
    }
}
我们重新回忆一下TitleFragment类,如果用户点击了一个列表项并且当前布局中没有包含一个R.id.details的view(DetailFragment所属view),那么application必须启动DetailActivity来显示被选中商品的详情。
下面是这个DetailsActivity,当屏幕竖屏时这个activity简单地内嵌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();
        }
    }
}
注意,如果屏幕是横向的DetailActivity会finish它自己,以至于main activity可以接管并且在TitleFragment的旁边显示DetailsFragment。当屏幕是竖向时刚刚开始启动DetailsActivity立马屏幕又转换为了横向的这种情况是有可能发生的。

你可能感兴趣的:(Fragment,官方文档)