Android魔幻之旅(四):管理Fragment

本文内容:
  • 设计哲学
  • 创建fragment
  • 添加用户界面
  • 向Activity添加fragment
  • 管理fragment
  • 执行fragment事务
    • FragmentTransaction中的常用方法
    • Fragment的静态加载
  • 与Activity通信
    • 创建对Activity的事件回调
    • 向App Bar添加菜单
  • 处理fragment生命周期
    • 与 Activity 生命周期协调一致

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

Fragment必须始终嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。 例如,当 Activity 暂停时,其中的所有Fragment也会暂停;当 Activity 被销毁时,所有Fragment也会被销毁。 不过,当 Activity 正在运行时(处于onResume的生命周期状态),您可以独立操纵每个Fragment,如添加或移除它们。 当您执行此类fragment事务时,您也可以将fragment添加到由 Activity 管理的返回栈——Activity后退栈中的每一条记录都是一条已发生的fragment事务。 返回栈让用户可以通过按返回按钮撤消Fragment事务(后退)。

当您将fragment作为 Activity 布局的一部分添加时,它存在于 Activity 视图层次结构的某个 ViewGroup 内部,并且Fragment会定义其自己的视图布局。您可以通过在 Activity 的布局文件中声明Fragment,将其作为 元素插入您的 Activity 布局中,或者通过将其添加到某个现有 ViewGroup,利用应用代码进行插入。不过,fragment并非必须成为 Activity 布局的一部分;您还可以将没有自己 UI 的fragment用作 Activity 的不可见工作线程。

本文描述如何在开发您的应用时使用fragment,包括将fragment添加到 Activity 返回栈时如何保持其状态、如何与 Activity 及 Activity 中的其他fragment共享事件、如何为 Activity 的操作栏发挥作用等等。

设计哲学


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

例如,新闻应用可以使用一个fragment在左侧显示文章列表,使用另一个fragment在右侧显示文章—— 两个fragment并排显示在一个 Activity 中,每个fragment都具有自己的一套生命周期回调方法,并各自处理自己的用户输入事件。 因此,用户不需要使用一个 Activity 来选择文章,然后使用另一个 Activity 来阅读文章,而是可以在同一个 Activity 内选择文章并进行阅读,如图 1 中的平板电脑布局所示。

图 1

您应该将每个fragment都设计为可重复使用的模块化 Activity 组件。也就是说,由于每个fragment都会通过各自的生命周期回调来定义其自己的布局和行为,您可以将一个fragment加入多个 Activity,因此,您应该采用可复用式设计,避免直接从某个fragment直接操纵另一个fragment。 这特别重要,因为模块化fragment让您可以通过更改fragment的组合方式来适应不同的屏幕尺寸。 在设计可同时支持平板电脑和手机的应用时,您可以在不同的布局配置中重复使用fragment,以根据可用的屏幕空间优化用户体验。 例如,在手机上,如果不能在同一 Activity 内储存多个 fragment ,可能必须利用单独fragment来实现单窗格 UI。

创建fragment


要想创建fragment,您必须创建 Fragment 的子类(或已有的Fragment子类)。Fragment 类的代码与 Activity 非常相似。它包含与 Activity 类似的回调方法,如 onCreate()、onStart()、onPause() 和 onStop()。实际上,如果您要将现有 Android 应用转换为使用fragment,可能只需将代码从 Activity 的回调方法移入fragment相应的回调方法中。

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

onCreate()
系统会在创建 fragment 时调用此方法。在实现内应该初始化想在ragment暂停或停止后恢复时保留的必需fragment组件。

onCreateView()
系统会在fragment首次绘制其UI时调用此方法。 要想为您的fragment绘制 UI,您从此方法中返回的 View 必须是fragment布局的根视图。如果fragment未提供 UI,您可以返回 null。

onPause()
系统将此方法作为用户离开fragment的第一个信号(但并不总是意味着此fragment会被销毁)进行调用。 您通常应该在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。

大多数应用都应该至少为每个fragment实现这三个方法,但您还应该使用几种其他回调方法来处理fragment生命周期的各个阶段。

图 2

您可能还想扩展几个子类,而不是 Fragment 基类:

DialogFragment
显示浮动对话框。使用此类创建对话框可有效地替代使用 Activity 类中的对话框帮助程序方法,因为您可以将片段对话框纳入由 Activity 管理的片段返回栈,从而使用户能够返回清除的片段。

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

PreferenceFragment
以列表形式显示 Preference 对象的层次结构,类似于 PreferenceActivity。这在为您的应用创建“设置” Activity 时很有用处。

添加用户界面

Fragment 通常用作 Activity UI的一部分,将其自己的布局融入 Activity。要想为fragment提供布局,您必须实现 onCreateView() 回调方法,Android 系统会在需要绘制其布局时调用该方法。您对此方法的实现返回的 View 必须是片段布局的根视图。

:如果fragment是 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 参数是fragment布局将插入到的父 ViewGroup(来自 Activity 的布局)。savedInstanceState 参数是在恢复片段时,提供上一片段实例相关数据的 Bundle。

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

  • 您想要扩展的布局的资源 ID;
  • 将作为扩展布局父项的 ViewGroup。传递 container 对系统向扩展布局的根视图(由其所属的父视图指定)应用布局参数具有重要意义;
  • 指示是否应该在扩展期间将扩展布局附加至 ViewGroup(第二个参数)的布尔值。(在本例中,其值为 false,因为系统已经将扩展布局插入 container —— 传递 true 值会在最终布局中创建一个多余的视图组。)
    现在,您已经了解了如何创建提供布局的fragment。接下来,您需要将该fragment添加到您的 Activity 中。

向Activity添加fragment

通常,fragment向宿主 Activity 贡献一部分 UI,作为 Activity 总体视图层次结构的一部分嵌入到 Activity 中。可以通过两种方式向 Activity 布局添加fragment:

  • 在 Activity 的布局文件内声明fragment

在本例中,您可以将fragment当作视图来为其指定布局属性。 例如,以下是一个具有两个fragment的 Activity 的布局文件:



    
    

中的 android:name 属性指定要在布局中实例化的 Fragment 类。

  • 或者通过编程方式将 fragment 添加到某个现有 ViewGroup

您可以在 Activity 运行期间随时将 fragment 添加到 Activity 布局中。您只需指定要将 fragment 放入哪个 ViewGroup。

要想在您的 Activity 中执行 fragment (如添加、移除或替换 fragment ),您必须使用 FragmentTransaction 中的 API。您可以像下面这样从 Activity 获取一个 FragmentTransaction 实例:

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

然后,您可以使用 add() 方法添加一个 fragment ,指定要添加的 fragment 以及将其插入哪个视图。例如:

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

传递到 add() 的第一个参数是 ViewGroup,即应该放置 fragment 的位置,由资源 ID 指定,第二个参数是要添加的 fragment 。
一旦您通过 FragmentTransaction 做出了更改,就必须调用 commit() 以使更改生效。

添加没有 UI 的 fragment

例展示了如何向您的 Activity 添加 fragment 以提供 UI。不过,您还可以使用 fragment 为 Activity 提供后台行为,而不显示额外 UI。

要想添加没有 UI 的 fragment ,请使用 add(Fragment, String) 从 Activity 添加 fragment (为 fragment 提供一个唯一的字符串“标记”,而不是视图 ID)。 这会添加 fragment ,但由于它并不与 Activity 布局中的视图关联,因此不会收到对 onCreateView() 的调用。因此,您不需要实现该方法。

并非只能为非 UI 片段提供字符串标记 — 您也可以为具有 UI 的片段提供字符串标记 — 但如果片段没有 UI,则字符串标记将是标识它的唯一方式。如果您想稍后从 Activity 中获取fragment,则需要使用 findFragmentByTag()。

如需查看将没有 UI 的fragment用作后台工作线程的示例 Activity,请参阅 FragmentRetainInstance.java 示例,该示例包括在 SDK 示例(通过 Android SDK 管理器提供)中,以
/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java 形式位于您的系统中。

管理fragment


要想管理您的 Activity 中的 fragment ,您需要使用 FragmentManager。要想获取它,请从您的 Activity 调用 getFragmentManager()。

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

  • 通过 findFragmentById()(对于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(对于提供或不提供 UI 的片段)获取 Activity 中存在的 fragment 。
  • 通过 popBackStack()(模拟用户发出的返回命令)将片段从返回栈中弹出。
  • 通过 addOnBackStackChangedListener() 注册一个侦听返回栈变化的侦听器。

如需了解有关这些方法以及其他方法的详细信息,请参阅 FragmentManager 类文档。

执行fragment事务


提交给 Activity 的每组更改都称为事务,您可以使用 FragmentTransaction 中的 API 来执行一项事务。您也可以将每个事务保存到由 Activity 管理的返回栈内,从而让用户能够回退 fragment (类似于回退 Activity)。

从 FragmentManager 获取一个 FragmentTransaction 实例:

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

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

不过,在您调用 commit() 之前,您可能想调用 addToBackStack(),以将事务添加到片段事务返回栈。 该返回栈由 Activity 管理,允许用户通过按返回按钮返回上一片段状态。

例如,以下示例说明了如何将一个 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 会替换目前在 R.id.fragment_container ID 所标识的布局容器中的任何 fragment (如有)。通过调用 addToBackStack() 可将替换事务保存到返回栈,以便用户能够通过按返回按钮撤消事务并回退到上一 fragment 。

如果您向事务添加了多个更改(如又一个 add() 或 remove()),并且调用了 addToBackStack(),则在调用 commit() 前应用的所有更改都将作为单一事务添加到返回栈,并且返回按钮会将它们一并撤消。

向 FragmentTransaction 添加更改的顺序无关紧要,不过:

  • 您必须最后调用 commit()
  • 如果您要向同一容器添加多个 fragment ,则您添加 fragment 的顺序将决定它们在视图层次结构中的出现顺序

如果您没有在执行移除 fragment 的事务时调用 addToBackStack(),则事务提交时该 fragment 会被销毁,用户将无法回退到该 fragment 。 否则(即调用了addToBackStack()),则系统会停止该调用 addToBackStack(),并在用户回退时将其恢复。

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

调用 commit() 不会立即执行事务,而是在 Activity 的 UI 线程(“主”线程)可以执行该操作时再安排其在线程上运行。不过,如有必要,您也可以从 UI 线程调用 executePendingTransactions() 以立即执行 commit() 提交的事务。通常不必这样做,除非其他线程中的作业依赖该事务。

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

FragmentTransaction中的常用方法:

  • add():往Activity中添加一个Fragment。
  • remove():从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈,这个Fragment实例将会被销毁。
  • replace():使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体。
  • hide():隐藏当前的Fragment,仅仅是设为不可见,并不会销毁。
  • show():显示之前隐藏的Fragment
  • detach():将此Fragment从Activity中分离,会销毁其布局,但不会销毁该实例。
  • attach():将从Activity中分离的Fragment,重新关联到该Activity,重新创建其视图层次。
  • commit():提交一个事务。

Fragment的静态加载

以上使用是动态加载fragment,还可以如下静态加载fragment。
静态加载Fragment分三步:

  1. 创建一个Layout,作为要静态加载的Fragment的布局。
  2. 创建一个类继承Fragment,然后重写里面的onCreateView方法,View对象为刚刚创建的Layout。
  3. 在Layout布局文件中声明fragment,android:name属性里是我们上面创建的类,另外,fragment必须用id或tag作为唯一标识。

与 Activity 通信


尽管 Fragment 是作为独立于 Activity 的对象实现,并且可在多个 Activity 内使用,但fragment的给定实例会直接绑定到包含它的 Activity。

具体地说,fragment可以通过 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的事件回调

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

例如,如果一个新闻应用的 Activity 有两个 fragment —— 一个用于显示文章列表( fragment A),另一个用于显示文章( fragment B),那么 fragment A 必须在列表项被选定后告知 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);
    }
    ...
}

如果 Activity 未实现接口,则 fragment 会引发 ClassCastException。实现时,mListener 成员会保留对 Activity 的 OnArticleSelectedListener 实现的引用,以便 fragment A 可以通过调用 OnArticleSelectedListener 接口定义的方法与 Activity 共享事件。例如,如果 fragment A 是 ListFragment 的一个扩展,则用户每次点击列表项时,系统都会调用 fragment 中的 onListItemClick(),然后该方法会调用 onArticleSelected() 以与 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)用来从应用的 ContentProvider 获取文章的 ID。

Fragment之间使用Bundle传递数据

FragmentOne:在用户点击按钮时,将fragment2添加到当前页面显示出来,并传递一个Bundle对象。

@Override
public void onClick(View v)
{
    //FragmentTwo是从FragmentOne即将跳转到的第二个Fragment
    FragmentTwo fTwo = new FragmentTwo();
    //利用Bundle传递“key”中保存的参数Path中数据
    Bundle bundle = new Bundle();
    bundle.putString("key", Path);
    fTwo.setArguments(bundle);

    getFragmentManager().beginTransaction()
    .replace(R.id.id_content, fTwo, "TWO");
    .addToBackStack(null);
    .commit();
}

FragmentTwo:在FragmentTwo中获取传递的参数:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.right, null);
    TextView textView = (TextView) view.findViewById(R.id.textView1);
    Bundle bundle = getArguments();
    if (bundle != null) {
        String item = bundle.getString("key");
        textView.setText(item);
    }
    return view;
}

向App Bar添加菜单

您的fragment可以通过实现 onCreateOptionsMenu() 向 Activity 的选项菜单(并因此向应用栏)贡献菜单项。不过,为了使此方法能够收到调用,您必须在 onCreate() 期间调用 setHasOptionsMenu(),以指示片段想要向选项菜单添加菜单项(否则,fragment将不会收到对 onCreateOptionsMenu() 的调用)。

您之后从fragment添加到选项菜单的任何菜单项都将追加到现有菜单项之后。 选定菜单项时,fragment还会收到对 onOptionsItemSelected() 的回调。

您还可以通过调用 registerForContextMenu(),在fragment布局中注册一个视图来提供浮动上下文菜单。用户打开上下文菜单时,fragment会收到对 onCreateContextMenu()}的调用。当用户选择某个菜单项时,fragment会收到对 onContextItemSelected() 的调用。

处理fragment生命周期


管理fragment生命周期与管理 Activity 生命周期很相似。和 Activity 一样,fragment也以三种状态存在:

  • Resume
    fragment在运行中的 Activity 中可见。
  • Pause
    另一个 Activity 位于前台并具有焦点,但此fragment所在的 Activity 仍然可见(前台 Activity 部分透明,或未覆盖整个屏幕)。
  • Stop
    fragment不可见。宿主 Activity 已停止,或fragment已从 Activity 中移除,但已添加到返回栈。 停止fragment仍然处于存活状态(系统会保留所有状态和成员信息)。 不过,它对用户不再可见,如果 Activity 被终止,它也会被终止。
    同样与 Activity 一样,假使 Activity 的进程被终止,而您需要在重建 Activity 时恢复片段状态,您也可以使用 Bundle 保留片段的状态。您可以在fragment的 onSaveInstanceState() 回调期间保存状态,并可在 onCreate()、onCreateView() 或 onActivityCreated() 期间恢复状态。

Activity 生命周期与fragment生命周期之间的最显著差异在于它们在其各自返回栈中的存储方式。 默认情况下,Activity 停止时会被放入由系统管理的 Activity 返回栈(以便用户通过返回按钮回退到 Activity,任务和返回栈对此做了阐述)。然而,仅当您在移除fragment的事务执行期间通过调用 addToBackStack() 显式请求保存实例时,系统才会将fragment放入由宿主 Activity 管理的返回栈。

在其他方面,管理fragment生命周期与管理 Activity 生命周期非常相似。 因此,管理 Activity 生命周期的做法同样适用于fragment。 但您还需要了解 Activity 的生命周期对fragment生命周期的影响。

注意:如需 Fragment 内的某个 Context 对象,可以调用 getActivity()。但要注意,请仅在fragment附加到 Activity 时调用 getActivity()。如果fragment尚未附加,或在其生命周期结束期间分离,则 getActivity() 将返回 null。

与 Activity 生命周期协调一致

图 3

fragment所在的 Activity 的生命周期会直接影响fragment的生命周期,其表现为,Activity 的每次生命周期回调都会引发每个fragment的类似回调。 例如,当 Activity 收到 onPause() 时,Activity 中的每个fragment也会收到 onPause()。

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

  • onAttach()
    在fragment已与 Activity 关联时调用(Activity 传递到此方法内)。
  • onCreateView()
    调用它可创建与fragment关联的视图层次结构。
  • onActivityCreated()
    在 Activity 的 onCreate() 方法已返回时调用。
  • onDestroyView()
    在移除与fragment关联的视图层次结构时调用。
  • onDetach()
    在取消fragment与 Activity 的关联时调用。

图 3 图示说明了受其宿主 Activity 影响的fragment生命周期。在该图中,您可以看到 Activity 的每个连续状态如何决定fragment可以收到的回调方法。 例如,当 Activity 收到其 onCreate() 回调时,Activity 中的fragment只会收到 onActivityCreated() 回调。

一旦 Activity 达到resumed状态,您就可以随意向 Activity 添加fragment和移除其中的fragment。 因此,只有当 Activity 处于resumed状态时,fragment的生命周期才能独立变化。

不过,当 Activity 离开resumed状态时,fragment会在 Activity 的推动下再次经历其生命周期。

Fragment的向下兼容

  1. 当对Fragment引用包的时候,会有两个选项,android.app.Fragment和android.support.v4.app.Fragment,其中android.support.v4.app.Fragment就是为了兼容低版本(当API Version 低于11时)而考虑的。
  2. 使用android.support.v4.app.Fragment时,Activity需要继承FragmentActivity,如果Activity继承了AppCompatActivity就不用了,AppCompatActivity已经继承了FragmentActivity。

顺便推荐上关于Fragment的一个系列:
Fragment完全解析系列一
Fragment完全解析系列二
Fragment之我的解决方案

你可能感兴趣的:(Android魔幻之旅(四):管理Fragment)