Android内部分享[10]——Android中Fragment的使用

Fragment 概述

Fragment 表示 FragmentActivity 中用户活动和界面的一部分,也称为界面碎片。你可以在一个 Activity 中包含多个 Fragment 来构建你的 UI 界面,这样你就可以抽取出来一些可以共用的 Fragment 了,Fragment 和 Activity 一样同样具有自己的生命周期,也可以定义自己的事件。

Fragment 必须始终驻留在 Activity 中,并且 Fragment 的生命周期直接受到宿主 Activity 生命周期的影响。例如,当 Activity 处于 paused 生命周期时,其中的所有 Fragment 也将处于 paused,当 Activity 被销毁时,所有 Fragment 也暂停。然而,当一个 Activity 正在运行(它处于恢复的生命周期状态)时,您可以独立地操作每个 Fragment,例如添加或删除它们。当您执行这样一个 Fragment 事务时,您还可以将它添加到由 Activity 管理的后堆栈中,Activity 中的每个后堆栈条目都是片段 transac 的记录

当你创建的 Fragment 作为 Activity 布局的一部分添加时,它位于 Activity 视图层次结构中的 ViewGroup 中,Fragment 定义了自己的视图布局。您可以通过在 Activity 的布局文件中将片段声明为 <fragment> 元素,或者通过将应用程序代码添加到现有的 ViewGroup,将 Fragment 插入到 Activity 布局中。

Fragment 的设计原则

Android 在 Android 3.0 (API level 11) 中引入了 fragment,主要是为了在大屏幕上支持更动态、更灵活的 UI 设计,比如平板电脑。由于平板电脑的屏幕比手机大得多,因此有更多的空间来组合和交换 UI 组件。Fragment 允许这样的设计,而不需要管理视图层次结构的复杂更改。通过将 Activity 的布局划分为多个片段,您可以在运行时修改 Activity 的外观,并将这些更改保存在由 Activity 管理的后堆栈中。它们现在被广泛使用。

例如,新闻应用程序可以使用一个 Fragment 在左边显示文章列表,使用另一个 Fragment 在右边显示文章——两个片段并排出现在一个 Activity 中,每个 Fragment 都有自己的一组生命周期回调方法并处理自己的用户输入事件。因此,用户可以选择一篇文章并在同一个 Activity 中阅读,而不是使用一个 Activity 来选择一篇文章,如下图中的平板布局所示。

一个由fragment定义的两个UI模块如何组合成一个用于平板电脑设计的Activity

您应该将每个 Fragment 设计成一个模块化的、可重用的 Activity 组件。也就是说,因为每个 Fragment 都用自己的生命周期回调定义了自己的布局和行为,所以您可以在多个 Activity 中包含一个 Fragment,所以您应该为重用而设计,并避免直接操作另一个 Fragment 中的一个 Fragment。这一点尤其重要,因为模块化 Fragment 允许您针对不同的屏幕大小更改 Fragment 组合。在设计支持平板电脑和手机的应用程序时,您可以在不同的布局配置中重用您的 Fragment,以根据可用的屏幕空间优化用户体验。例如,在手机上,当一个以上的 Fragment 不能容纳在同一个 Activity 中时,可能需要分离 Fragment 来提供单窗格用户界面。

例如上面的新闻应用程序示例,当在平板电脑大小的设备上运行时,应用程序可以在 Activity A 中嵌入两个 Fragment。然而,在手机大小的屏幕上,没有足够的空间容纳这两个 Fragment,所以 Activity A 只包括文章列表的 Fragment,当用户选择一篇文章时,它启动 Activity B,Activity B 包括阅读文章的第二个 Fragment。因此,该应用程序通过以不同的组合重用 Fragment 来支持平板电脑和手机。

创建一个 Fragment

要创建 Fragment,您必须创建 Fragment 的子类(或其现有子类)。Fragment 类的代码看起来很像 Activity。它包含类似于 Activity 的回调方法,例如 onCreate()、onStart()、onPause() 和 onStop()。事实上,如果您正在将现有的安卓应用程序转换为使用 Fragment,您可以简单地将代码从 Activity 的回调方法移动到 Fragment 的相应回调方法中。

Fragment的生命周期(当它的Activity正在运行时)

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

onCreate()

系统在创建 Fragment 时调用这个。在您的实现中,您应该初始化 Fragment 的基本组件,当 Fragment 暂停(pause) 或停止(stop),然后恢复(resume)时,您希望保留的这些组件。

onCreateView()

当 Fragment 第一次绘制用户界面的时候,系统调用这个。要为 Fragment 绘制用户界面,您必须从该方法返回一个 View,该 View 是 Fragment 布局的根。如果 Fragment 不提供用户界面,您可以返回 null。

onPause()

系统调用这个方法作为用户离开 Fragment 的第一个指示(尽管它并不总是意味着 Fragment 被销毁)。这通常是您应该提交任何应该在当前用户会话之后保持的更改的地方(因为用户可能不会回来)。

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

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);
    }
}

要为 Fragment 提供布局,必须实现 onCreateView() 回调方法,当 Fragment 绘制布局时,Android 系统将调用该方法。该方法的实现必须返回一个视图,该视图是 Fragment 布局的根。

上面的 inflate 接收三个参数,含义如下:

  • 布局文件的 ID
  • 需要附加到 resource 资源文件的根控件
  • attachToRoot 为 true,就将这个 root 作为根对象返回,否则仅仅将这个 root 对象的 LayoutParams 属性附加到 resource 对象的根布局对象上

接下来我们向 Activity 中添加 Fragment,有两种方法:

方法一:在 Activity 的布局文件中声明该 Fragment。

<?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 返回的视图来代替 <fragment> 元素。

方法二:通过代码将 Fragment 添加到 Activity 已有的 ViewGroup 中。

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

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

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

管理 Fragment

要管理 Activity 中的 Fragment ,您需要使用 FragmentManager。要获得它,请从您的 Activity 中调用 getSupportFragmentManager()。FragmentManager 可以帮助我们做下面这几件事情。

  • 使用 findFragmentById() (用于在 Activity 布局中提供 UI 的 Fragment)或 findFragmentByTag()(用于提供或不提供 UI 的 Fragment)获取 Activity 中存在的 Fragment。
  • 使用 popBackStack()(模拟用户的 back 命令) 从后堆栈中取出 Pop Fragment。
  • 使用 addOnBackStackChangedListener() 注册一个侦听器,以便对后堆栈进行更改。

您还可以使用 FragmentManager 打开一个 FragmentTransaction,它允许您执行事务,例如添加和删除 Fragment. 每个事务都是您希望同时执行的一组更改。您可以使用 add()、remove() 和 replace() 等方法设置要为给定事务执行的所有更改。然后,要将事务应用于 Activity ,必须调用commit()。

然而,在调用 commit() 之前,您可能需要调用 addToBackStack(),以便将事务添加到 Fragment 事务的后堆栈中。该回栈由 Activity 管理,并允许用户通过单击 back 按钮返回到以前的 Fragment 状态。

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getSupportFragmentManager().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();

如果您向事务添加多个更改,例如另一个 add() 或 remove(),并调用 addToBackStack(),那么在调用 commit() 之前应用的所有更改都将作为单个事务添加到后堆栈中,并且后退按钮将它们全部反转。如果要将多个 Fragment 添加到同一个 ViewGroup 中,则添加它们的顺序决定了它们在视图层次结构中出现的顺序。

如果您在执行删除 Fragment 的事务时没有调用 addToBackStack(),那么当事务提交时,该 Fragment 将被销毁,用户无法导航回该 Fragment。然而,如果您在移除 Fragment 时确实调用了addToBackStack(),那么 Fragment 将被停止,如果用户导航回来,Fragment 将在稍后恢复。

调用 commit() 不会立即执行事务。相反,只要线程能够运行,它就会调度它在 Activity 的 UI 线程(主线程)上运行。但是,如果需要,您可以从 UI 线程调用 executePendingTransactions() 来立即执行 commit() 提交的事务。除非事务是其他线程中的作业的依赖项,否则通常没有必要这样做。

Fragment 和 Activity 数据传递

如果你当前的 Activity 是 FragmentActivity 则可以很容易的获取 Activity 的实例。

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

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

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

我们可以给 Fragment 创建事件回调,这样可以很容易的实现接口隔离和数据传递:

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

Activity 和 Fragment 生命周期关系

有三种状态下 Fragment 是处于活动状态的, 分别是 ResumedPausedStopped.

Activity生命周期对Fragment生命周期的影响

Resumed

Fragment 在运行的 Activity 中是可见的。

Paused

另一个 Activity 位于前台并具有焦点,但是该 Fragment 所在的 Activity 仍然可见 (前台 Activity 是部分透明的,或者不覆盖整个屏幕)。

Stopped

Fragment 不可见。要么主机活动已停止,要么 Fragment 已从 Activity 中删除,但已添加到后堆栈。停止的 Fragment 仍然是活动的(系统保留所有状态和成员信息)。但是,它对用户不再可见,如果 Activity 被终止,它就会被终止。

Activity 和 Fragment 生命周期中最显著的区别是如何存储在各自的后堆栈中。默认情况下,Activity 在停止时被放入由系统管理的活动的后堆栈中(以便用户可以使用后退按钮导航回该活动,如任务和后堆栈中所述)。但是,只有当您在移除 Fragment 的事务期间通过调用 addToBackStack() 显式请求保存实例时,Fragment 才会被放入由主机活动管理的后堆栈中。