• Fragment


    commitAllowingStateLoss(),从名字上就能看出,这种提交是允许状态值丢失的。

    Fragments

    英文原文: http://developer.android.com/guide/components/fragments.html

    采集日期:2014-12-31

    在本文中

    1. 设计理念
    2. 创建 Fragment
      1. 添加用户界面组件
      2. 把 Fragment 加入 Activity
    3. 管理 Fragment
    4. 执行 Fragment 事务
    5. 与 Activity 通讯
      1. 创建 Activity 的事件回调方法
      2. 在 Action Bar 中添加菜单项
    6. 处理 Fragment 的生命周期
      1. 与 Activity 的生命周期合作
    7. 示例

    关键类

    1. Fragment
    2. FragmentManager
    3. FragmentTransaction

    参阅

    1. 用 Fragment 创建动态 UI
    2. 同时支持平板电脑和手机

    Fragment 代表 Activity 当中的一项操作或一部分用户界面。 一个 Activity 中的多个 Fragment 可以组合在一起,形成一个多部分拼接而成的用户界面组件,并可在多个 Activity 中复用。一个 Fragment 可被视为 Activity 中一个模块化的部分, 它拥有自己的生命周期,并接收自己的输入事件,在 Activity 运行过程中可以随时添加或移除它 (有点类似“子 Activity”,可在不同的 Activity 中重用)。

    Fragment 必须嵌入某个 Activity 中,其生命周期直接受到宿主 Activity 生命周期的影响。 例如,当 Activity 被暂停(Paused)时,其内部所有的 Fragment 也都会暂停。 而当 Activity 被销毁时,它的 Fragment 也都会被销毁。 不过,在 Activity 运行期间(生命周期状态处于 恢复(Resumed) 状态时),每一个 Fragment 都可以被独立地操作,比如添加或移除。 在执行这些操作事务时,还可以将它们加入该 Activity 的回退栈(Back Stack)中 — Activity 回退栈的每个入口就是一条操作过的 Fragment 事务记录。 回退堆栈使得用户可以通过按下 回退(Back) 键来回退 Fragment 事务(后退一步)。

    当把 Fragment 加入 Activity 布局(Layout) 后,它位于 Activity View 层次架构(Hierarchy)的某个 ViewGroup 里,且拥有自己的 View 布局定义。 通过在 Activity 的 Layout 文件中声明 <fragment> 元素,可以在 Layout 中添加一个 Fragment。 也可以用程序代码在已有的 ViewGroup 中添加一个 Fragment。 不过, Fragment 并不一定非要是 Activity 布局的一部分,它也可以没有自己的界面,而是用作 Activity 的非可视化工作组件。

    本文介绍了如何创建应用程序并使用 Fragment , 包括: Fragment 在加入 Activity 的回退栈后如何维护自身的状态、 如何与 Activity 及同一 Activity 中的其他 Fragment 共享事件、 如何构建 Activity 的 Action Bar,等等。

    设计理念

    Android 自 3.0 (API 级别 11)开始引入 Fragment,主要用来在平板电脑之类的大屏幕设备上提供更加动态、灵活的用户界面设计。 因为平板电脑的屏幕要比手机大得多,可以有更多的空间来组合和变换 UI 组件的位置。 有了 Fragment ,就可以在设计界面时不用去维护 View 层次架构的复杂变化。 通过把 Activity 的布局拆分到多个 Fragment 中去,可以在运行时修改 Activity 的外观,还可以把这些变动保存到 Activity 的回退栈中。

    例如,某个新闻应用可以在左侧用一个 Fragment 显示文章列表,在右侧用另一个 Fragment 显示文章内容, 这两个 Fragment 并排显示在同一个 Activity 中,每个 Fragment 拥有各自的生命周期回调方法,并处理自己的用户输入事件。 这样,就不是用一个 Activity 选择文章、另一个 Activity 阅读文章了,用户可以在同一个 Activity 中完成这两个操作, 图1 给出了平板电脑上的布局示意。

    每个 Fragment 都应被设计为模块化、可复用的 Activity 组件。 也就是说,由于每个 Fragment 都定义了自己的布局, 并用自己的生命周期回调方法定义了自己的运行方式, 一个 Fragment 可以被放入多个 Activity 中去, 所以它应该被设计为可复用的,并避免从一个 Fragment 中直接操作另一个 Fragment。 这一点非常重要,因为模块化的 Fragment 可以实现不同屏幕尺寸下对 Fragment 组合的修改。 在设计通用于平板电脑和手机的应用程序时,可以在各种不同的布局下复用 Fragment ,以便根据可用屏幕空间的大小优化用户的体验。 例如,在手机上,如果在同一个 Activity 中放不下多个 Fragment , 就可能有必要把多个 Fragment 分开,界面上只显示单个组件。

    图 1.两个由 Fragment 定义的 UI 模块示例,在平板电脑上可以并入一个 Activity 中显示,而在手机上则可以分开显示。

    例如 — 继续以上面的新闻应用为例 — 如果在平板电脑尺寸的设备上运行,此应用可以把两个 Fragment 都嵌入 Activity A 中显示。 而在手机尺寸的屏幕上,空间不足以同时容纳两个 Fragment , 所以 Activity A 就仅包含一个文章列表 Fragment , 用户选中某篇文章后,就会打开 Activity B ,里面放置了阅读文章用的第二个 Fragment 。 这样,通过以不同的组合方式复用 Fragment ,应用程序就能同时支持平板电脑和手机了,如图 1 所示。

    关于针对不同屏幕参数设计不同的 Fragment 组合,详情请参阅指南 同时支持平板电脑和手机 。

    创建 Fragment

    图 2.Fragment 的生命周期(当其 Activity 正在运行时)

    要创建 Fragment ,必须创建一个 Fragment 对象(或者它的已存在子类)的子类。 Fragment 类的代码与 Activity 非常相像。它包含了一些与 Activity 类似的回调方法,比如 onCreate() 、 onStart() 、 onPause() 和 onStop() 。 实际上,如果要把某个已有的 Android 应用转换成使用 Fragment 的应用,只要把 Activity 回调方法中的代码移入对应的 Fragment 方法中去即可。

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

    onCreate()
    系统将在创建 Fragment 时调用本方法。 在本方法的代码中,应该对那些在 Fragment 暂停或停止时需要保存状态的必要组件进行初始化。
    onCreateView()
    系统将在 Fragment 第一次绘制用户界面时调用本方法。 为了绘制 Fragment 的用户界面,必须让本方法返回一个 View ,用作 Fragment 布局的根元素。 如果 Fragment 没有用户界面,则可以返回 null 。
    onPause()
    只要用户有要离开 Fragment 的迹象(尽管这并不意味着一定会销毁 Fragment),系统将会首先调用本方法。 通常这时应该提交当前用户会话(Session)中需要保存的的所有改动(因为用户可能不会再回来了)。

    大部分应用程序都至少应该为每个 Fragment 实现以上三个方法, 为了能够应对 Fragment 各个生命周期状态的变化,还应该实现更多其他的回调方法。 所有的生命周期回调方法将在处理 Fragment 的生命周期一节中进行详细讨论。

    除了基类 Fragment 之外,还有其他一些子类可供扩展:

    DialogFragment
    显示一个浮动的对话框。 用这个类创建的对话框可以很好地替代 Activity 类的助手(Helper)方法创建的对话框, 因为 Fragment 对话框可以置入 Activity 的回退栈,这样用户就可以返回已经关闭的 Fragment 了。
    ListFragment
    显示一个列表框,其中的列表项由适配器(Adapter)提供(比如SimpleCursorAdapter ,这类似于 ListActivity )。本 Fragment 提供了很多管理列表 View 的方法,比如用于处理点击事件的 onListItemClick() 回调方法。
    PreferenceFragment
    以列表的方式显示一个由 Preference 对象组成的层次结构,类似于PreferenceActivity 。这在创建应用程序的“设置” Activity 时会很有用。

    添加用户界面

    Fragment 通常用作 Activity 用户界面的一部分,并且把自己的布局提供给 Activity 使用。

    要为 Fragment 提供一个布局,必须实现 onCreateView() 回调方法,当 Fragment 需要绘制自己的布局时, Android 系统将会调用该方法。 该方法必须返回一个 View ,用作 Fragment 布局的根元素 。

    注意:如果 Fragment 是 ListFragment 的子类, onCreateView() 的默认代码将返回一个 ListView ,这时就不必再自行实现这个 View 了。

    要让 onCreateView() 返回一个布局 ,可以从Layout 资源中读取 XML 定义并生成。 onCreateView() 里有一个 LayoutInflater 对象,可有助于完成建立布局的工作。

    下面给出了一个 Fragment 子类的例子,它从 example_fragment.xml 文件中载入布局定义:

    public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
     Bundle savedInstanceState) {
    // 载入 Fragment 的布局
    return inflater.inflate(R.layout.example_fragment, container, false);
    }
    }

    创建布局

    在以上例子中, R.layout.example_fragment 引用了一个名为example_fragment.xml 的布局资源,资源存放于应用程序的资源目录中。 关于如何创建 XML 格式的布局定义,请参阅 用户界面 文档。

    传给 onCreateView() 的 container 参数是当前 Fragment 布局的上一级ViewGroup (来自 Activity 的布局),Fragment 的布局定义都将插入其中。savedInstanceState 参数是一个 Bundle 对象,如果该 Fragment 是被恢复运行,这里面给出了前一次 Fragment 实例的有关数据(关于还原状态的更多信息,在处理 Fragment 的生命周期一节中讨论)。

    inflate() 方法有三个参数:

    • 用来生成布局的资源 ID。
    • 要生成的布局的上一级 ViewGroup 。传入 container 是非常重要的,系统用它作为新生成布局的根 View ,它将被设为新布局的父 View 。
    • 布尔值,指明了新生成的布局是否要与 ViewGroup (第二个参数)绑定。 (在此例中设为 False ,因为系统已经是要把新生成的布局插入到 container 中去了。 这里如果传入 True 将会在最终的布局中创建一个多余的 ViewGroup 。)

    这就是创建一个自带布局的 Fragment 的过程。接下来,需要把 Fragment 加入到 Activity 中去。

    把 Fragment 加入 Activity

    通常,Fragment 向宿主 Activity 提供了一些用户界面,这些 UI 嵌入到 Activity 整体的 View 层次结构中,成为其中的组成部分。 把 Fragment 加入 Activity 布局的途径有两种:

    • 在 Activity 布局文件中声明 Fragment

      这种情况下,可以把 Fragemt 视为 View ,指定其 Layout 属性。 比如,下面是包含两个 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 布局时,系统会实例化布局中每一个 Fragment,并调用每个 Fragment 的 onCreateView() 方法以读取各自的布局定义。 系统会在<fragment> 元素的位置直接插入 Fragment 返回的 View 。

      注意:每个 Fragment 的标识符都必须唯一,当重启 Activity 时,系统可以用此标识符来恢复 Fragemt (还可以用此标识符来记录 Fragment 状态,以便执行移除之类的事务操作。 给 Fragment 赋予 ID 的方式有三种:

      • 为 android:id 属性给定一个唯一的 ID。
      • 为 android:tag 属性给定一个唯一的字符串。
      • 如果前两者都未指定,系统就采用父容器 View 的 ID。
    • 或者,还可以通过程序代码在已有的 ViewGroup 内添加 Fragment。

      在 Activity 运行的任何时刻,都可以在 Activity 布局中添加 Fragment 。 只需要指定放置 Fragment 的 ViewGroup 即可。

      要想在 Activity 中执行 Fragment 事务(如添加、移除、替换 Fragment),必须使用 FragmentTransaction 提供的 API 来完成。 可以采用如下途径从 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 即可。 第二个参数是要添加的 Fragment。

      一旦用 FragmentTransaction 做出了改动,就必须调用 commit() 让改动生效。

    添加不带用户界面的 Fragment

    上述例子演示了如何在 Activity 中添加一个提供用户界面的 Fragment 。 然而,还可以用 Fragment 为 Activity 执行一个不带界面的后台操作。

    要为 Activity 添加一个不带用户界面的 Fragment,请使用 add(Fragment, String) (这里要为 Fragment 指定唯一的字符串标签“tag”,而不是 View 的 ID )。 因为未与 Activity 布局中的任何 View 关联,所以此类 Fragment 不会接收到onCreateView() 调用。这样就不需要实现此方法了。

    为 Fragment 定义的字符串标签并不是只能用于无界面的 Fragment,带有用户界面的 Fragment 也可以给定字符串标签。 但是,如果 Fragment 没有用户界面,字符串标签就是标识它的唯一方式。 如果以后需要从 Activity 中获取该 Fragment ,就要用到findFragmentByTag() 了。

    关于使用不带界面的 Fragment 执行后台操作的 Activity 示例,请参阅例程FragmentRetainInstance.java

    管理 Fragment

    要对 Activity 中的 Fragment 进行管理,需要用到 FragmentManager 。它可以通过调用 Activity 的 getFragmentManager() 方法来获取。

    通过 FragmentManager 可以完成的操作包括:

    • 用 findFragmentById() 方法获取 Activity 中已存在的 Fragment (适用于向 Activity 布局提供了用户界面的 Fragment ),或者用 findFragmentByTag() 方法获取(不论是否提供界面都可使用)。
    • 用 popBackStack() 方法将 Fragment 从回退栈中弹出(模拟由用户发起的回退Back 指令)。
    • 用 addOnBackStackChangedListener() 注册一个用于监视回退栈变化的侦听器。

    关于这些方法的更多详情,请参阅 FragmentManager 类的文档。

    在上一节的示例中,可以用 FragmentManager 启动一个 FragmentTransaction,这样就可以执行一些诸如添加和移除之类的事务了。

    执行 Fragment 事务

    在 Activity 中使用 Fragment 的一大好处,就是可以根据用户的需求对其进行添加、移除、替换及其他操作。 提交给 Activity 的一组操作被称作一个事务,可以通过FragmentTransaction 提供的 API 来执行事务。 每一个事务都可被保存到 Activity 的回退栈中,用户可以回退这些 Fragment 操作(类似于回退多个 Activity 一样)。

    以下演示了从 FragmentManager 中获取一个 FragmentTransaction 的实例:

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

    每个事务由一系列需要一起完成的操作组成。 可以在一个事务中执行多个操作,诸如 add() 、 remove() 和 replace() 等。然后,必须调用 commit() 向 Activity 提交事务。

    为了能把某个事务加入 Fragment 事务的回退栈中, 可以在 commit() 之前先调用addToBackStack() 。这个回退栈是由 Activity 管理的,以便用户可以按下 Back 按钮返回之前的 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 替换了 ID 为 R.id.fragment_container 的布局容器中的当前 Fragment 。 通过调用 addToBackStack() ,这次替换操作作为事务被保存到回退栈中,这样用户就可以按下 Back 键回滚事务,回到前一个 Fragment 中。

    如果在一个事务中加入了多步操作(比如更多的 add() 或 remove() 操作),并且调用了 addToBackStack() ,那么在 commit() 之前的全部这些操作都会作为同一个事务进入回退栈,按下 Back 键后将会回滚全部操作。

    同一个 FragmentTransaction 中的操作与加入顺序无关,除了:

    • commit() 必须最后调用;
    • 如果在同一个容器中添加了多个 Fragment ,那么添加的顺序决定了在 View 层次结构中的显示顺序。

    如果在执行移除 Fragment 事务时没有调用 addToBackStack() ,那么事务提交后该 Fragment 将会被销毁,用户就无法再返回了。 反之,如果移除前调用了addToBackStack() ,那么 Fragment 将会被 停止 ,当用户返回时将会被恢复。

    提示:每个 Fragment 事务都可以应用一种过渡动画效果,这通过在提交事务前调用setTransition() 来实现。

    调用 commit() 后事务并不是立即被执行的,而是被排入 Activity UI 线程(主线程)的运行计划,一有空闲就会执行。 不过在必要时,也可以调用当前 UI 线程的executePendingTransactions() ,以便立即执行已提交的事务。 通常没必要这么做,除非有其他线程中的任务在等待该事务的完成。

    警告:只有在 Activity保存状态之前才能利用 commit() 来提交 Fragment 事务。 如果在之后提交会抛出异常。这是因为在恢复 Activity 时,事务提交之后的状态可能会丢失。 如果丢失提交内容也没什么关系,请使用 commitAllowingStateLoss() 。

    与 Activity 通讯

    虽然 Fragment 是独立于 Activity 实现的对象,且一个 Fragment 可在多个 Activity 中使用,但对某个 Fragment 实例而言,是与其所在的 Activity 绑定在一起的。

    具体来说, Fragment 可以通过 getActivity() 访问 Activity 实例,很容易就能实现查找 Activity 布局中的 View 之类的任务:

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

    同理, Activity 也可以通过 FragmentManager 的 findFragmentById() 或findFragmentByTag() 来获取一个 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,以便通知 Fragment B 显示文章内容。 这种情况下,就可在 Fragment A 中定义一个 OnArticleSelectedListener 接口:

    publicstaticclassFragmentAextendsListFragment{
        ...
        // 容器 Activity 必须实现该接口
        publicinterfaceOnArticleSelectedListener{
            publicvoid onArticleSelected(Uri articleUri);
        }
        ...
    }

    然后,Fragment 的宿主 Activity 实现了 OnArticleSelectedListener 接口,并重写(Override) onArticleSelected() 方法,以便将 Fragment A 的事件发送给 Fragment B。 为了确保宿主 Activity 必须实现该接口,在 Fragment A 的onAttach() 回调方法(系统会在把 Fragment 添加到 Activity 之后调用)中,通过对传入 onAttach() 的 Activity 进行强制类型转换(Cast),尝试实例化OnArticleSelectedListener 对象:

    publicstaticclassFragmentAextendsListFragment{
        OnArticleSelectedListener mListener;
        ...
        @Override
        publicvoid onAttach(Activity activity){
            super.onAttach(activity);
            try{
                mListener =(OnArticleSelectedListener) activity;
            }catch(ClassCastException e){
                thrownewClassCastException(activity.toString()+" must implement OnArticleSelectedListener");
            }
        }
        ...
    }

    如果 Activity 没有实现接口, Fragment 将会抛出异常 ClassCastException 。 如果转换成功,成员变量 mListener 中就指向了 Activity 已实现的OnArticleSelectedListener , 这样 Fragment A 就可以通过调用OnArticleSelectedListener 接口中定义的方法,与 Activity 分享事件了。 例如,假定 Fragment A 扩展自 ListFragment ,当用户每次点击列表项时,系统就会调用 Fragment 的 onListItemClick() 方法,然后由它调用 onArticleSelected() 与 Activity 分享事件。

    publicstaticclassFragmentAextendsListFragment{
        OnArticleSelectedListener mListener;
        ...
        @Override
        publicvoid 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 Provider。

    向 Action Bar 添加菜单项

    通过实现 onCreateOptionsMenu() ,Fragment 可以为 Activity 的 选项菜单 (及Action Bar )提供菜单项。 不过,要让该方法能接收到调用,必须在 onCreate()方法中调用 setHasOptionsMenu() ,以便声明该 Fragment 将要在“选项”(Options)菜单中添加菜单项(否则,Fragment 将不会收到onCreateOptionsMenu() ) 调用。

    所有由 Fragment 添加到“选项”菜单中去的菜单项,都将追加到已有菜单项的后面。 当选中某个菜单项时, Fragment 同时还会收到 onOptionsItemSelected() 调用。

    通过调用 registerForContextMenu() ,还可以把 Fragment 布局中的某个 View 注册为上下文(Context)菜单。 当用户打开此菜单时, Fragment 将会接收到一次onCreateContextMenu() . When the user selects an item, the fragment receives a call to onContextItemSelected() 调用。

    注意:当用户选中某个菜单项时,虽然 Fragment 是会收到“on-item-selected”的回调,但 Activity 将首先收到相应的回调。 如果 Activity 的“on-item-selected”回调方法的代码中没有对选中项进行处理,事件才会传给 Fragment 回调。 “选项”菜单和上下文菜单都是如此。

    关于菜单的更多信息,请参阅开发指南中的菜单和Action Bar章节。

    处理 Fragment 的生命周期

    图 3.Activity 生命周期对 Fragment 生命周期的影响

    Fragment 生命周期的管理与 Activity 的非常相像。 与 Activity 类似, Fragment 可能处于三种状态:

    恢复(Resumed)
    Fragment 在当前 Activity 中可见。
    暂停(Paused)
    其他 Activity 处于前台并获得焦点,但 Fragment 所在的 Activity 仍然可见 (前台 Activity 是部分透明的或者未完全遮挡整个屏幕)。
    停止(Stopped)
    Fragment 不可见。也许是宿主 Activity 已被停止,也许 Fragment 已从 Activity 中移除且被加入回退栈中。 已被停止的 Fragment 仍然是存活的(所有的状态和成员信息都被系统保存着)。 不过,用户看不到此 Fragment 了,当 Activity 被杀死时 Fragment 也会被杀死。

    与 Activity 类似,在 Activity 进程被杀死并被重新创建,且需要恢复 Fragment 的状态时, 可以利用 Bundle 取回 Fragment 的状态。 可以在 Fragment 的onSaveInstanceState() 回调方法中保存 Fragment 的状态,并在 onCreate() 、onCreateView() 或 onActivityCreated() 方法中恢复状态。 关于保存状态的更多信息,请参阅文档Activity。

    Activity 与 Fragment 生命周期之间的最明显区别,就是在各自的回退栈中的保存方式。 当 Activity 被停止时,它默认会被存放在系统管理的 Activity 回退栈中(如任务和回退栈所述,用户可以用 Back 键回退回去)。 然而,仅当在移除 Fragment 的事务中调用 addToBackStack() 显式地请求保存实例, Fragment 才会被放入由宿主 Activity 管理的回退栈中。

    除此之外, Fragment 生命周期的管理与 Activity 非常类似。 因此, 管理 Activity 生命周期 一文中介绍的做法同样也适用于 Fragment 。 当然,还有必要了解 Activity 生命周期对 Fragment 生命周期的影响。

    提醒:如果需要在 Fragment 中使用 Context 对象,可以调用 getActivity()。 但是请注意,只能在 Fragment 附着于 Activity 之后,才能进行调用。 当 Fragment 还没有与 Activity 关联之前,或者在生命周期结束被解除关联之后, getActivity()将会返回 null。

    与 Activity 的生命周期合作

    宿主 Activity 的生命周期直接影响着 Fragment 的生命周期, Activity 的所有生命周期回调方法都会触发其中每一个 Fragment 的对应回调方法。 比如,当 Activity 收到onPause() 时,其中的每一个 Fragment 都会收到 onPause() 调用。

    不过,Fragment 还拥有一些自己的生命周期回调方法,这些方法只与 Activity 有关,用于执行创建或销毁 Fragment 界面之类的操作。 这包括:

    onAttach()
    当 Fragment 与 Activity 建立关联时将会调用(这里会传入 Activity )。
    onCreateView()
    创建与 Fragment 关联的 View 层次架构。
    onActivityCreated()
    当 Activity 的 onCreate() 方法返回时将会调用。
    onDestroyView()
    当与 Fragment 关联的 View 层次架构被删除时将会调用。
    onDetach()
    当 Fragment 与 Activity 解除关联时将会调用。

    图3中给出了 Fragment 生命周期的流程,及其与 宿主 Activity 之间的关系。 从图中可知, Activity 所处的各个状态决定了 Fragment 可能收到的回调方法。 比如,当 Activity 已收到过 onCreate() 调用之后,该 Activity 中的 Fragment 将不再会收到onActivityCreated() 调用了。

    只要 Activity 进入了恢复(Resumed)状态,就可以在其中自由添加或移除 Fragment 了。 也只有在 Activity 处于已恢复状态时,Fragment 的生命周期才可以脱开 Activity 而独自变化。

    不过,一旦 Activity 离开了已恢复状态, Fragment 的生命周期就又被 Activity 所左右了。

    示例

    以下示例将把本文所述综合在一起展示,这里的 Activity 用到两个 Fragment 创建了双板块布局。 在下面的 Activity 中,一个 Fragment 显示了莎士比亚戏剧列表,另一个 Fragment 显示了列表选中剧目的简介。 并且,还演示了如何根据屏幕配置设置 Fragment 的参数。

    注意:本例的完整源码见 FragmentLayout.java 

    按惯例,主 Activity 在 onCreate() 中应用了一个布局:

    @Override
    protectedvoid onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
    
        setContentView(R.layout.fragment_layout);
    }

    布局由 fragment_layout.xml 文件给出:

    <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"android:layout_height="match_parent">
    
        <fragmentclass="com.example.android.apis.app.FragmentLayout$TitlesFragment"
                android:id="@+id/titles"android:layout_weight="1"
                android:layout_width="0px"android:layout_height="match_parent"/>
    
        <FrameLayoutandroid: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)将会占据屏幕右侧的空间,但一开始是空的。 如下所示,只有当用户在列表中选择了一项,才会在FrameLayout 中放入一个 Fragment 。

    然而,不是所有的屏幕都能同时把剧目表和简介并排显示出来。 所以上述布局只适用于横向屏幕,即保存到 res/layout-land/fragment_layout.xml 。

    但是,当屏幕为纵向模式,系统将会使用保存在res/layout/fragment_layout.xml 中的以下布局:

    <FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"android:layout_height="match_parent">
        <fragmentclass="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 并靠它来完成大部分列表框(List View)的操作。

    在查看这部分代码时请注意,当用户点击某个列表项时,可能要执行两种操作: 根据当前的布局不同,或是在同一个 Activity 中新建并显示一个展现戏剧简介的 Fragment (在 FrameLayout 中添加 Fragment),或是启动一个新的 Activity (用于显示 Fragment)。

    publicstaticclassTitlesFragmentextendsListFragment{
        boolean mDualPane;
        int mCurCheckPosition =0;
    
        @Override
        publicvoid onActivityCreated(Bundle savedInstanceState){
            super.onActivityCreated(savedInstanceState);
    
            // Populate list with our static array of titles.
            setListAdapter(newArrayAdapter<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
        publicvoid onSaveInstanceState(Bundle outState){
            super.onSaveInstanceState(outState);
            outState.putInt("curChoice", mCurCheckPosition);
        }
    
        @Override
        publicvoid 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 =newIntent();
                intent.setClass(getActivity(),DetailsActivity.class);
                intent.putExtra("index", index);
                startActivity(intent);
            }
        }
    }

    第二个 Fragment 的代码,用于显示 TitlesFragment 内所选剧目的简介:

    publicstaticclassDetailsFragmentextendsFragment{
        /**
         * Create a new instance of DetailsFragment, initialized to
         * show the text at 'index'.
         */
        publicstaticDetailsFragment newInstance(int index){
            DetailsFragment f =newDetailsFragment();
    
            // Supply index input as an argument.
            Bundle args =newBundle();
            args.putInt("index", index);
            f.setArguments(args);
    
            return f;
        }
    
        publicint getShownIndex(){
            return getArguments().getInt("index",0);
        }
    
        @Override
        publicView 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.
                returnnull;
            }
    
            ScrollView scroller =newScrollView(getActivity());
            TextView text =newTextView(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 View (也就是 DetailsFragment ),那么应用程序将会启动DetailsActivity 来显示列表项的有关内容。

    下面是 DetailsActivity 类,当屏幕处于纵向模式时,这里只是简单地把DetailsFragment 嵌进来而已,用于显示所选剧目的简介。

    publicstaticclassDetailsActivityextendsActivity{
    
        @Override
        protectedvoid 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 =newDetailsFragment();
                details.setArguments(getIntent().getExtras());
                getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
            }
        }
    }

    请注意,此 Activity 在屏幕横向放置时将会自动终止,以便让主 Activity 接替它把DetailsFragment 显示在 TitlesFragment 旁边。 如果用户在打开DetailsActivity 时是纵向模式,然后旋转到横向模式(这时会重新启动当前 Activity),就会发生这种情况。

    关于使用 Fragment 的更多示例(以及本例的完整代码),请参阅 ApiDemos 中的 API 演示例程(可在 SDK 组件实例 )下载。

    http://www.tuicool.com/articles/faArEru

  • 相关阅读:
    二元树的深度 【微软面试100题 第五十二题】
    和为n连续正数序列 【微软面试100题 第五十一题】
    一道看上去很吓人的算法题 【微软面试去100题 第四十九题】
    在左移的递减数组中查找某数 【微软面试100题 第四十八题】
    最长递减子序列 【微软面试100题 第四十七题】
    括号问题 【微软面试100题 第四十六题】
    矩阵运算 【微软面试100题 第四十五题】
    设计一个魔方(六面)的程序 【微软面试100题 第四十四题】
    二叉搜索树的非递归前中后序遍历 【微软面试100题 第四十三题】
    合并链表 【微软面试100题 第四十二题】
  • 原文地址:https://www.cnblogs.com/manmanlu/p/5614247.html
Copyright © 2020-2023  润新知