Android官方文档—APP组件(Activities)(Fragments)

Fragments(片段)

片段表示Activity中的一部分行为或用户界面。您可以在单个activity中组合多个片段以构建多窗格UI,也可以在多个activity中重用片段。您可以将片段视为activity的模块化部分,它具有自己的生命周期,接收自己的输入事件,并且可以在activity运行时添加或删除(有点像“子activity”,您可以在不同activity中重复使用)。

片段必须始终嵌入到activity中,片段的生命周期直接受主机activity生命周期的影响。例如,当activity暂停时,其中的所有片段也都会暂停,当activity被销毁时,所有片段也都会被销毁。但是,当一个activity正在运行时(它处于resumed的生命周期状态),您可以独立操作每个碎片,例如添加或删除它们。执行此类片段事务时,还可以将其添加到由activity管理的后台堆栈中 - 当发生的片段事务,activity中的后台堆栈都会有所记录。后向堆栈允许用户通过按“返回”按钮来回退片段事务(向后导航)。

将片段添加为activity布局的一部分时,它将位于activity视图层次结构内的ViewGroup中,并且片段需要定义自己的视图布局。 您可以通过在activity的布局文件中声明<fragment>元素来将片段插入到您的activity布局中。或通过在代码中将其添加到现有ViewGroup中。但是,片段不一定是activity布局的一部分;您也可以使用没有自己的UI的片段作为activity的隐形工作者。

本文档描述了如何构建应用程序以使用片段,包括片段在添加到activity的后台堆栈时如何维持其状态,与activity和activity中的其他片段共享事件,扩展activity的操作栏等等。

设计理念 


Android在Android 3.0(API级别11)中引入了片段,主要是为了在大屏幕(如平板电脑)上支持更加动态和灵活的UI设计。由于平板电脑的屏幕比手机的屏幕大得多,因此组合和交换UI组件的空间更大。片段允许此类设计,而无需您管理对视图层次结构的复杂更改。通过将activity的布局划分为片段,您可以在运行时修改activity的外观,并将这些更改保留在由activity管理的后台堆栈中。

例如,新闻应用程序可以使用一个片段显示左侧文章列表,另一个片段显示右侧文章 - 两个片段并排显示在一个activity中,每个片段都有自己的生命周期集回调方法并处理自己的用户输入事件。因此,用户可以选择一篇文章并在同一activity中全部阅读,而不是使用一个activity来选择文章和另一个activity来阅读文章,如图1中的平板电脑布局所示。

您应该将每个片段设计为模块化和可重用的activity组件。也就是说,因为每个片段在自己的生命周期回调中定义自己的布局和自己的行为,所以可以在多个activity中重用一个片段,因此您应该设计成重用的并避免在一个片段直接操作另一个片段。这一点尤其重要,因为模块化片段允许根据不同屏幕大小来组合不同片段。在设计支持平板电脑和手机的应用程序时,您可以在不同的布局配置中重复使用片段,以根据可用的屏幕空间优化用户体验。例如,在手机上,当多个片段不能放入同一activity时,可能需要将这些片段分开以单独提供UI。

图1.对于平板通过片段定义的两个UI模块组合在同一个activity,但是对于手机分开设计的示例。 

例如,继续使用新闻应用程序示例 - 当在平板电脑大小的设备上运行时,应用程序可以在activity A中嵌入两个片段。然而,在手机大小的屏幕上,没有足够的空间放置两个片段,因此activity A仅包括文章列表的片段,当用户选择文章时,它启动activity B,其中包括要阅读这篇文章的第二个片段。因此,该应用程序通过重复使用不同组合的片段来支持平板电脑和手机,如图1所示。

有关使用不同屏幕配置的不同片段组合设计应用程序的更多信息,请参阅支持平板电脑和手机指南。

创建片段


图2.片段的生命周期(当它的Activity正在运行时)。

要创建片段,您必须创建Fragment的子类(或其现有的子类)。Fragment类的代码看起来很

像Activity。它包含类似于activity 的回调方法,例如onCreate(),onStart(),onPause()和onStop()。实际上,如果您要将现有的Android应用程序转换为使用片段,则可以简单地将代码从您的activity的回调方法移动到片段的相应回调方法中。

通常,您应该至少实现以下生命周期方法:

onCreate()

系统在创建片段时调用此方法。在这个实现中,您应该初始化在片段暂停或停止时需要要保留的的基本信息。

onCreateView()

当片段第一次绘制其用户界面时,系统会调用此方法。要为片段绘制UI,必须从此方法返回视图,该视图是片段根布局。如果片段不提供UI,则可以返回null。

onPause()

系统将此方法称为用户离开片段的第一个回调(尽管并不总是意味着片段被销毁)。这通常是您应该提交保存用户操作状态的地方(因为用户可能不会回来)。

大多数应用程序应该为每个片段至少实现这三种方法,但是还应该使用其他几种回调方法来处理片段生命周期的各个阶段。有关处理片段生命周期的部分中将更详细地讨论所有生命周期回调方法。

您可能还想要扩展一些子类,而不是基本Fragment类:

DialogFragment

显示浮动对话框。使用此类创建对话框比使用对话框助手创建方法更好,因为您可以将片段对话框合并到由活动管理的片段的后台堆栈中,从而允许用户返回到被隐藏的片段中。

ListFragment

显示由适配器管理的item列表(例如SimpleCursorAdapter),类似于ListActivity。它提供了几种管理列表视图的方法,例如onListItemClick()回调来处理点击事件。

PreferenceFragment

将Preference对象的层次结构显示为列表,类似于PreferenceActivity。在为应用程序创建“设置”activity 时,这非常有用。

添加用户界面

片段通常用作activity 用户界面的一部分,并在activity 提供自己的布局。

要为片段提供布局,必须实现onCreateView()回调方法,当片段绘制其布局时,Android系统会调用该方法。您对此方法的实现必须返回一个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参数是将插入片段的父ViewGroup(来自activity 的布局)。如果片段正在恢复,savedInstanceState参数是一个Bundle,它提供有关片段的前一个实例的数据(恢复状态将在有关处理片段生命周期的部分中进行更多讨论)。

inflate()方法有三个参数:

  • 要扩充的布局的资源ID。
  • ViewGroup是膨胀布局的父级。传递container非常重要,以便系统将布局参数应用于所要膨胀布局的根视图,该布局由其所在的父视图指定。
  • 一个布尔值,指示在充气期间是否应将膨胀的布局附加到ViewGroup(第二个参数)。(在这种情况下,应该传递false,因为系统已经将膨胀的布局插入到容器中 - 传递true将在最终布局中创建冗余视图组)。

现在您已经了解了如何创建提供布局的片段。接下来,您需要将片段添加到您的activity中。

将片段添加到activity

通常,片段为宿主activity提供UI的一部分,作为宿主activity的一部分视图嵌入。您可以通过两种方式将片段添加到activity布局:

  • 在activity的布局文件中声明片段。

在这种情况下,您可以为片段指定布局属性,就像它是视图一样。例如,这是包含两个片段的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类。

当系统创建此活动布局时,它会实例化布局中指定的每个片段,并为每个片段调用onCreateView()方法,以检索每个片段的布局。系统直接将片段所返回的视图替换掉<fragment>元素。

注意:每个片段都需要一个唯一的标识符,如果重新启动activity,系统可以使用该标识符来恢复片段(您可以使用它来获取片段以执行事务,例如删除它)。有三种方法可以为片段提供ID:

  • 提供具有唯一ID的android:id属性。
  • 为android:tag属性提供唯一的字符串。
  • 如果您不提供前两个,系统将使用container view的ID。
  • 或者,以代码的方式将片段添加到现有ViewGroup。

在activity运行的任何时候,您都可以将片段添加到activity布局中。您只需指定一个ViewGroup来放置片段。

要在activity中进行片段事务(例如添加,删除或替换片段),必须使用FragmentTransaction中的API。您可以从Activity中获取FragmentTransaction的实例,如下所示:

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

然后,您可以使用add()方法添加片段,指定要添加的片段以及要插入的视图。例如:

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

传递给add()的第一个参数是应放置片段的V​​iewGroup,由资源ID指定,第二个参数是要添加的片段。 

使用FragmentTransaction进行更改后,必须调用commit()才能使更改生效。

添加没有UI的片段

上面的示例显示了如何向activity添加片段以提供UI。但是,您也可以使用片段为activity提供后台行为,而无需显示其他UI。

要添加没有UI的片段,请使用add(Fragment,String)从activity中添加片段(为片段提供唯一的字符串“tag”,而不是视图ID)。这会添加片段,但由于它与activity布局中的视图无关,因此它不会接收对onCreateView()的调用。所以你不需要实现那个方法。

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,但如果片段没有UI,则字符串标记是唯一的方法识别它。如果您想稍后要从activity中获取片段,则需要使用findFragmentByTag()。

对于使用片段作为后台工作者而没有UI的示例activity,请参阅FragmentRetainInstance.java示例,该示例包含在SDK示例中(可通过Android SDK Manager获得)并位于您的系统上<sdk_root> / /APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java

管理片段


要管理activity中的片段,需要使用FragmentManager。要获取它,请从您的activity中调用getFragmentManager()。

您可以使用FragmentManager执行的一些操作包括:

  • 使用findFragmentById()(对于在activity布局中提供UI的片段)或findFragmentByTag()(对于提供或不提供UI的片段),获取activity中存在的片段。
  • 使用popBackStack()(用户模拟Back命令)从后端堆栈弹出片段。
  • 使用addOnBackStackChangedListener()为后备栈注册一个监听器以进行更改。

有关这些方法和其他方法的更多信息,请参阅FragmentManager类文档

如上一节所示,您还可以使用FragmentManager打开FragmentTransaction,它允许您执行事务,例如添加和删除片段。

执行片段事务


在您的activity中使用片段的一个很棒的功能是能够添加,删除,替换和执行其他操作,以响应用户交互。您为activity提交的每组更改都称为事务,您可以使用FragmentTransaction中的API执行一项更改。您还可以将每个事务保存到由activity管理的后台堆栈中,允许用户向后导航片段更改(类似于向后导航activity)。

您可以像这样从FragmentManager获取FragmentTransaction的实例:

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

每个事务都是您希望同时执行的一组更改。您可以使用add(),remove()和replace()等方法设置要为给定事务执行的所有更改。然后,要将事务应用于activity,必须调用commit()。

但是,在调用commit()之前,您可能希望调用addToBackStack(),以便将事务添加到片段事务的后台堆栈中。此后备堆栈由activity管理,并允许用户通过按“返回”按钮返回到先前的片段状态。

例如,以下是如何将一个片段替换为另一个片段,并保留后栈中的先前状态:

// 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 ID标识的布局容器中的任何片段(如果有)。通过调用addToBackStack(),替换事务将保存到后台,因此用户可以通过按“返回”按钮来反转事务并恢复上一个片段。

如果向事务添加多个更改(例如另一个add()或remove())并调用addToBackStack(),则在调用commit()之前应用的所有更改都将作为单个事务和Back按钮添加到后台堆栈中将它们全部扭转。

将更改添加到FragmentTransaction的顺序无关紧要,但是:

  • 你必须最后调用commit()。
  • 如果要将多个片段添加到同一容器中,则添加它们的顺序将决定它们在视图层次结构中的显示顺序。

如果在执行删除片段的事务时未调用addToBackStack(),则在提交事务并且用户无法导航回到该片段时,该片段将被销毁。然而,如果在删除片段时调用addToBackStack(),则片段将停止,并且如果用户导航回复则将恢复。

提示:对于每个片段事务,您可以通过在提交之前调用setTransition()来应用过渡动画。

调用commit()不会立即执行事务。相反,它会尽快的在activity的UI线程(“主”线程)上运行。但是,如果需要,可以从UI线程调用executePendingTransactions()以立即执行commit()提交事务。除非事务依赖于其他线程,否则通常不需要这样做。

警告:您只能在activity保存其状态(用户离开activity时)之前使用commit()提交事务。如果在该点之后尝试提交,则将引发异常。这是因为如果需要恢复activity,则提交后的状态可能会丢失。对于可以丢失提交的情况,请使用commitAllowingStateLoss()。

与Activity通信


尽管Fragment是作为一个独立于Activity的对象实现的,并且可以在多个activity中使用,但是片段的某个实例直接与包含它的activity相关联。

具体来说,片段可以使用getActivity()访问Activity实例,并轻松执行任务,例如在activity布局中查找视图:

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

同样,您的activity可以通过使用findFragmentById()或findFragmentByTag()从FragmentManager获取对Fragment的引用来调用片段中的方法。例如:

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

创建Activity的事件回调

在某些情况下,您可能需要一个片段来与活动共享事件。一种好方法是在片段内定义一个回调接口,并要求宿主Activity实现它。当Activity通过接口收到回调时,它可以根据需要与布局中的其他片段共享信息。

例如,如果新闻应用程序在Activity中有两个片段 - 一个用于显示文章列表(片段A)而另一个用于显示文章(片段B) - 那么片段A必须在选择列表项时告知Activity让它可以告诉片段B显示文章。在这种情况下,OnArticleSelectedListener接口在片段A中声明:

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

然后,宿主Activity实现OnArticleSelectedListener接口并覆盖onArticleSelected()以从片段A通知片段B事件。为确保宿主Activity实现此接口,片段A的onAttach()回调方法(系统调用时)将片段添加到活动中)通过强制转换传递给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尚未实现接口,则片段将抛出ClassCastException。如果Activity尚实现了接口,则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,Activity(或其他片段)用于从应用程序的content  provider中获取文章。

Content Providers文档中提供了有关使用content provider的更多信息。

将Item添加到App Bar

您的片段可以通过实现onCreateOptionsMenu()将菜单项提供给Activity的“选项”菜单(以及App Bar)。但是,为了使此方法被调用,您必须在onCreate()期间调用setHasOptionsMenu(),以指示该片段要将项添加到选项菜单(否则,片段将不会接收对onCreateOptionsMenu()的调用)。

然后,您从片段添加到“选项菜单”的任何项目都将附加到现有菜单项。当选择菜单项时,片段还接收对onOptionsItemSelected()的回调。

您还可以在片段布局中注册视图,以通过调用registerForContextMenu()来提供上下文菜单。当用户打开上下文菜单时,片段将接收对onCreateContextMenu()的调用。当用户选择一个项目时,该片段接收对onContextItemSelected()的调用。

注意:虽然您的片段会为其添加的每个菜单项收到项目选择的回调,但当用户选择菜单项时,活动首先接收相应的回调。如果活动的on-item-selected回调的实现不消费事件,则事件将传递给片段的回调。选项菜单和上下文菜单也是如此。

有关菜单的更多信息,请参阅菜单开发人员指南和App Bar练习类。

处理片段生命周期


图3.Activity生命周期对片段生命周期的影响。

管理片段的生命周期很像管理Activity的生命周期。如Activity一样,片段可以以三种状态存在:

Resumed

片段在运行Activity中可见。

Paused

另一个Activity位于前景并具有焦点,但此片段所在的Activity仍然可见(前景Activity部分透明或未覆盖整个屏幕)。

Stopped

片段不可见。宿主Activity已停止或片段已从Activity中删除但已添加到后台堆栈。停止的片段仍然存在(系统保留所有状态和成员信息)。但是,它不再对用户可见,并且如果Activity被杀死片段也将被杀死。

也像Activity一样,您可以使用Bundle保留片段的状态,以防Activity的进程被终止时您需要在重新创建活动时恢复片段状态。您可以在片段的onSaveInstanceState()回调期间保存状态,并在onCreate(),onCreateView()或onActivityCreated()期间恢复它。有关保存状态的更多信息,请参阅“Activities”文档。

Activity和片段之间生命周期中最显着的差异是如何将其存储在其各自的后台堆栈中。默认情况下,当Activity stopped时被置于由系统管理的活动的后堆栈中(以便用户可以使用“后退”按钮导航回到它,如任务和后台堆栈中所述)。然而,只有当您在删除片段的事务期间通过调用addToBackStack()显式请求保存实例时,才会将片段放入由宿主Activity 管理的后台堆栈中。

因此,管理片段生命周期与管理Activity生命周期非常相似。因此,管理Activity 生命周期的方案也适用于片段。但是,您还需要了解的是,Activity 的生命周期如何影响片段的生命周期。

警告:如果在Fragment中需要Context对象,则可以调用getActivity()。但是,只有在片段附加到活动时才要小心调用getActivity()。当片段尚未附加,或在其生命周期结束时被分离时,getActivity()将返回null。

与Activity生命周期协调

片段所在Activity的生命周期直接影响片段的生命周期,因此Activity的每个生命周期回调都会导致每个片段的类似回调。例如,当Activity收到onPause()时,Activity中的每个片段都会收到onPause()。

但是,片段还有一些额外的生命周期回调,用于处理与Activity的唯一交互,以便执行创建和销毁片段UI等操作。这些额外的回调方法是:

onAttach()

当片段与Activity相关联时调用(此处传递Activity)。

onCreateView()

调用以创建与片段关联的视图层次结构。

onActivityCreated()

在活动的onCreate()方法返回时调用。

onDestroyView()

在销毁与片段关联的视图层次结构时调用。

onDetach()

当片段与活动分离时调用。

图3说明了片段生命周期的流程,因为它受到宿主Activity的影响。在此图中,您可以看到Activity的每个状态变化时如何确定片段可以接收哪些回调方法。例如,当Activity收到onCreate()回调时,Activity中的片段只接收onActivityCreated()回调。

一旦Activity达到resume状态,您就可以自由地向Activity添加和删除片段。因此,只有当活动处于resume状态时,片段的生命周期才能独立地改变。

但是,当Activity离开resume状态时,该片段将再次依赖于Activity影响其生命周期。

示例


为了将本文档中讨论的所有内容放在一起,下面是使用两个片段创建双窗格布局的活动示例。下面的活动包括一个片段,用于显示莎士比亚戏剧标题列表,另一个用于显示从列表中选择的播放摘要。它还演示了如何根据屏幕配置提供不同的片段配置。

注意:FragmentLayout.java中提供了此Activity的完整源代码

在onCreate()中,main 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(用于显示播放摘要的片段将在其中)会占据屏幕右侧的空间,但起初仍然是空的。正如您将在下面看到的那样,直到用户从列表中选择一个片段放入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以显示摘要,而不是加载第二个片段。

接下来,您可以看到如何在片段类中完成此操作。首先是TitlesFragment,它显示了莎士比亚戏剧名单。此片段扩展了ListFragment并依赖它来处理大多数列表视图工作。

在查看此代码时,请注意当用户单击列表项时有两种可能的行为:根据两个布局中的哪一个处于活动状态,它可以创建并显示一个新片段以在同一活动中的显示详细信息(添加片段到FrameLayout),或者开始一个新的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);
        }
    }
}

第二个片段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();
        }
    }
}

请注意,如果配置是横向的,则此activity将自行结束掉,以便主activity可以接管并在TitlesFragment旁边显示DetailsFragment。如果用户在纵向方向上启动DetailsActivity,但随后旋转到横向(重新启动当前活动),则会发生这种情况。

有关使用片段的更多示例(以及此示例的完整源文件),请参阅ApiDemos中提供的API演示示例应用程序(可从Samples SDK组件下载)。

猜你喜欢

转载自blog.csdn.net/weixin_42703445/article/details/82904835