全面总结之 Fragment 篇

什么是片段(Fragment)?

之前我们的控件都是放在一个布局中,但是 Android 系统拥有着各种尺寸的屏幕,小屏幕、大屏幕甚至电视,屏幕越大,可以容纳的 UI 组件越多,反之,屏幕越小,可以容纳的 UI 组件越少,这样对于 UI 的设计就很繁琐了,一套 UI 很难同时适应大屏幕和小屏幕,为了解决这个问题,Fragment 也就应用而生了。

Fragment 是一种可以镶嵌到 Activity 的一个 UI 片段,它可以让我们的 App 更加合理和充分的利用大屏幕控件。我们可以使用一个或者多个 Fragment 来组成界面,它有自己的生命周期,可以接受输入事件,并且我们可以动态的创建、移除 Fragment。

之前说了,Fragment 是镶嵌到 Activity 中的,所以它必须依附 Activity 而存在,并且其生命周期也受到宿主 Activity 影响。

创建并管理片段

创建 Fragment 很简单,让我们的类去继承 Fragment 或者其子类,然后重写其相关方法即可。

Fragment 需要重写的方法和 Activity 基本类似,事实上,如果要将现在的 App 转为使用 Fragment,只需要将代码从 Activity 的回调方法移入 Fragment 相应的回调方法中即可,通常,你至少需要实现以下几个方法:

  • onCreate():系统会在创建 Fragment 时候调用该方法,我们应该在这个方法中初始化 Fragment 在创建时或者停止后恢复需要保留的必须组件。

  • onCreateView():系统会在 Fragment 首次绘制界面的时候调用该方法。

  • onPause():系统将该方法作为用户离开片段的第一个信号(但并不意味着该 Fragment 就会被销毁)。通常情况下我们在该方法中确认在当前用户会话结束后仍然有效的任何更改。

添加界面

创建了 Fragment 的子类,我们还需要为我们的 Fragment 添加布局文件,这一步需要在 onCreateView 方法中操作,因为系统会在需要绘制布局的时候调用该方法,您对此方法的实现返回的 View 必须是片段布局的根视图。

要想从 onCreateView() 返回布局,您可以通过 XML 中定义的布局资源来扩展布局。为帮助您执行此操作,onCreateView() 提供了一个 LayoutInflater 对象。该对象的主要作用就是将我们的布局 XML 转换为 View 对象的。

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,因为系统已经将扩展布局插入 container — 传递 true 值会在最终布局中创建一个多余的视图组。)

至此,我们的 Fragment 已经创建成功,但是还需要将其添加到 Activity 中。

关于 LayoutInflater 对象的 inflate 方法,可以看这里:三个案例带你看懂LayoutInflater中inflate方法两个参数和三个参数的区别

向 Activity 添加 Fragment

有两个方法可以将 Fragment 添加到 Activity 当中:

  1. 在 Activity 的 XML 布局文件中添加:
<?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>

其实很简单,只需要在 Activity 的布局中间中,使用 <fragment> 标签将我们的 Fragment 添加进去即可,需要注意的是,我们 fragment 标签的 android:name 需要指向我们的 Fragment 类。

在上面的例子中,系统会依次调用每个 fragment 标签指定的类的 onCreateView 方法并使用该方法返回的 View 来替换 fragment 标签。

注:每个片段都需要一个唯一的标识符,重启 Activity 时,系统可以使用该标识符来恢复片段(您也可以使用该标识符来捕获片段以执行某些事务,如将其移除)。 可以通过三种方式为片段提供 ID:
●为 android:id 属性提供唯一 ID。
●为 android:tag 属性提供唯一字符串。
●如果您未给以上两个属性提供值,系统会使用容器视图的 ID。

在 Java 代码中添加 Fragment

在代码中添加 Fragment 也十分简单,我们先通过调用 Activity 的 getFragmentManager() 方法获取一个 FragmentManager 对象,然后再调用该 manager 对象的 beginTransaction() 方法,该方法返回一个 FragmentTransaction 对象,然后我们就可以调用该对象的 add()、replace() 和 delete() 方法来添加、替换和删除 Fragment 了。在我们对 FragmentTransaction 对象做了更改,需要调用其 commit() 方法使更改生效。

FragmentTransaction 对象提供了以下几个方法供我们在 Activity 中操作 Fragment:

  • add:添加,向 Activity 中添加 Fragment

    • add(Fragment fragment, String tag):调用 add(int, Fragment, String)方法,传入 containerViewId 为 0。

    • add(int containerViewId, Fragment fragment):通过调用 add(int, Fragment, String) 方法,传入 tag 为 null。

    • add(int containerViewId, Fragment fragment, String tag):添加一个 Fragment 到 Activity 的 id 为 containerViewId 的容器中,并设置其 tag。

  • replace:替换,替换 Activity 中的 Fragment

    • replace(int containerViewId, Fragment fragment, String tag):替换一个已经存在了的 Fragment 并设置其 tag(先remove,在add)。

    • replace(int containerViewId, Fragment fragment):调用 replace(int containerViewId, Fragment fragment, String tag),但是 tag 为 null。

  • remove:删除,删除 Activity 中的 Fragment

    • remove(Fragment fragment):移除一个已经存在了的Fragment。
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();

需要注意的是,当调用了 FragmentTransaction 对象的 commit 方法之后,如果还想继续再执行其他“添加”、“替换”或者“移除”操作的话,无法直接使用,需要重新获得 FragmentTranscation 对象。

在 Fragment 中模拟返回栈

我们试着在 Activity 当中先添加一个 Fragment,然后再使用别的 Fragment 替换这个 Fragment,这个时候我们按返回键,Activity 会直接退出,我们想要实现按下返回键,返回到上一个 Fragment 当中,就好像 Activity 一样。

FragmentTransaction 对象提供了一个 addToBackStack() 方法,在我们替换 Fragment 并且 commit 之前,调用 addToBackStack(),以将事务添加到片段事务返回栈。该返回栈由 Activity 管理,允许用户通过按返回按钮返回上一片段状态。

以下代码当替换掉 FirstFragment 之后,按下返回键,可以重新回到 FirstFragment。

public class MainActivity extends Activity implements View.OnClickListener {

    private Button bt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        bt = (Button) findViewById(R.id.bt_replace);
        bt.setOnClickListener(this);


        FragmentManager manager = getFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.add(R.id.layout, new FirstFragment());
        transaction.commit();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_replace:
                getFragmentManager().beginTransaction().replace(R.id.layout, new SecondFragment()).addToBackStack(null).commit();
                break;
        }
    }
}

我们可以向 Activity 中添加、替换、移除 Fragment,我们提交给 Activity 的每组更改都称之为事务,我们向事务添加了多个更改(如又一个 add() 或 remove()),并且调用了 addToBackStack(),则在调用 commit() 前应用的所有更改都将作为单一事务添加到返回栈,并且返回按钮会将它们一并撤消。

片段和活动的通信

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

片段的生命周期

Fragment 的生命周期和 Activity 很相似,Fragment 也以三种状态存在:

  • 运行:该状态下,Fragment 在运行中的 Activity 中可见。

  • 暂停:另一个 Activity 位于前台并具有焦点,但此 Fragment 所在的 Activity 仍然可见。

  • 停止:Fragment 不可见,宿主 Activity 已停止,或者 Fragment 已从 Activity 中移除,但已添加到返回栈。通知 Fragment 仍然处于活动状态(系统会保留所有状态和成员信息)。不过,它对用户不可见,如果 Activity 被终止,Fragment 也会被终止。

之前也说过了,Fragment 依附于 Activity 生存,用一张官方的图来展示 Activity 生命周期对于 Fragment 生命周期的影响。

Fragment 的生命周期和 Activity 声明周期最显著的差异就是,Activity 停止时会被放入由系统维护的 Activity 返回栈,而 Fragment 仅当您在移除片段的事务执行期间通过调用 addToBackStack() 显式请求保存实例时,系统才会将片段放入由宿主 Activity 管理的返回栈。

与 Activity 生命周期协调一致

Fragment 所在的 Activity 的生命周期会直接影响 Fragment 的生命周期,Activity 的每次生命周期回调都会引发每个 Fragment 类似的回调。例如,当 Activity 收到 onPause 时,该 Activity 中的每个 Fragment 也会收到 onPause。

例子:
我们在一个 Activity 里放置一个 Fragment,再他们的回调方法中打上 Log,然后我们看一下:

MainActivity:

public class MainActivity extends Activity {
    private Button bt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        bt = (Button) findViewById(R.id.bt);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, DialogActivity.class);
                startActivity(intent);
            }
        });

        FirstFragment fragment = new FirstFragment();
        FragmentManager manager = getFragmentManager();
        manager.beginTransaction().add(R.id.layout, fragment, "fragment").commit();

        Log.d("TTT", "Activity 的 onCreate 方法执行了");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d("TTT", "Activity 的 onStart 方法执行了");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d("TTT", "Activity 的 onResume 方法执行了");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d("TTT", "Activity 的 onPause 方法执行了");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d("TTT", "Activity 的 onStop 方法执行了");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("TTT", "Activity 的 onDestroy 方法执行了");
    }
}

FirstFragment:

public class FirstFragment extends Fragment {
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        Log.d("TTT", "Fragment 的 onAttach 方法执行了");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.d("TTT", "Fragment 的 onCreate 方法执行了");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {

        Log.d("TTT", "Fragment 的 onCreateView 方法执行了");
        return inflater.inflate(R.layout.fragment_first, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d("TTT", "Fragment 的 onActivityCreated 方法执行了");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d("TTT", "Fragment 的 onStart 方法执行了");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d("TTT", "Fragment 的 onResume 方法执行了");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d("TTT", "Fragment 的 onPause 方法执行了");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.d("TTT", "Fragment 的 onStop 方法执行了");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d("TTT", "Fragment 的 onDestroyView 方法执行了");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("TTT", "Fragment 的 onDestroy 方法执行了");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d("TTT", "Fragment 的 onDetach 方法执行了");
    }
}

执行程序,Log 如下:

06-14 01:49:09.103 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onCreate 方法执行了
06-14 01:49:09.110 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onAttach 方法执行了
06-14 01:49:09.110 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onCreate 方法执行了
06-14 01:49:09.110 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onCreateView 方法执行了
06-14 01:49:09.114 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onActivityCreated 方法执行了
06-14 01:49:09.114 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onStart 方法执行了
06-14 01:49:09.114 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onStart 方法执行了
06-14 01:49:09.114 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onResume 方法执行了
06-14 01:49:09.114 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onResume 方法执行了

点击按钮,跳转到一个窗口型的 Activity,Log 如下:

06-14 01:50:00.101 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onPause 方法执行了
06-14 01:50:00.101 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onPause 方法执行了

点击返回键,回到主界面:

06-14 01:50:30.795 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onResume 方法执行了
06-14 01:50:30.795 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onResume 方法执行了

点击 HOME 键盘,回到桌面:

06-14 01:51:01.497 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onPause 方法执行了
06-14 01:51:01.497 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onPause 方法执行了
06-14 01:51:01.508 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onStop 方法执行了
06-14 01:51:01.508 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onStop 方法执行了

再进入 App:

06-14 01:52:05.378 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onStart 方法执行了
06-14 01:52:05.378 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onStart 方法执行了
06-14 01:52:05.378 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onResume 方法执行了
06-14 01:52:05.379 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onResume 方法执行了

点击返回键,退出 App:

06-14 01:53:01.990 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onPause 方法执行了
06-14 01:53:01.990 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onPause 方法执行了
06-14 01:53:02.652 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onStop 方法执行了
06-14 01:53:02.652 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onStop 方法执行了
06-14 01:53:02.652 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onDestroyView 方法执行了
06-14 01:53:02.652 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onDestroy 方法执行了
06-14 01:53:02.652 10243-10243/com.li_xyz.fragmenttest D/TTT: Fragment 的 onDetach 方法执行了
06-14 01:53:02.652 10243-10243/com.li_xyz.fragmenttest D/TTT: Activity 的 onDestroy 方法执行了

可以看到,Fragment 的生命周期和 Activity 基本类似并且相伴随,只是片段还有几个额外的生命周期回调,用于处理与 Activity 的唯一交互,以执行构建和销毁片段 UI 等操作。 这些额外的回调方法是:

  • onAttach()
    在片段已与 Activity 关联时调用(Activity 传递到此方法内)。

  • onCreateView()
    调用它可创建与片段关联的视图层次结构。

  • onActivityCreated()
    在 Activity 的 onCreate() 方法已返回时调用。

  • onDestroyView()
    在移除与片段关联的视图层次结构时调用。

  • onDetach()
    在取消片段与 Activity 的关联时调用。

Fragment 的其他应用

添加没有 UI 的 Fragment

之前我们说的 Fragment 都是作为界面展示,其实 Fragment 可以不显示 UI,只是单纯的为 Activity 提供后台服务,有点儿类似于 Service。

要想添加没有 UI 的 Fragment,只需要调用 FragmentTransaction 对象的 add(Fragment fragment, String tag) 方法即可。这样添加的 Fragment 不会和 Activity 布局中的视图产生关联,因此不会收到对 onCreateView 方法调用,所以我们也不需要实现这个方法。

Activity:

public class MainActivity extends Activity {
    private Button bt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        bt = (Button) findViewById(R.id.bt);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FirstFragment fragment = (FirstFragment) getFragmentManager().findFragmentByTag("fragment");
                fragment.startDownload("http://www.baidu.com/test.txt");
            }
        });

        FirstFragment fragment = new FirstFragment();
        FragmentManager manager = getFragmentManager();
        manager.beginTransaction().add(fragment, "fragment").commit();

    }
}

FirstFragment:

public class FirstFragment extends Fragment {

    public void startDownload(final String uri) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d("TTT", "开始下载啦,下载连接是:" + uri);
                Log.d("TTT",Thread.currentThread().getName());
            }
        }).start();
    }
}

在这里举一个使用 Fragment 下载的例子,因为 Fragment 是依附于 Activity,所以它也是运行在主线程里的,执行下载这种耗时的操作,是不能在主线程里的。

将 Fragment 添加到菜单当中

Fragment 可以向 Activity 的菜单中添加选项,只需要执行以下步骤:

  1. 在我们的 Fragment 的 onCreate 方法中调用 setHasOptionsMenu(true)。

  2. 在 Fragment 中实现 onCreateOptionsMenu 方法。

  3. 如果希望在 Fragment 处理 item 的点击事件,可以实现 Fragment 的 onOptionsItemSelected 方法(也可以在 Activity 中处理 item 的点击事件)。

示例:

Activity:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        FirstFragment fragment = new FirstFragment();
        FragmentManager manager = getFragmentManager();
        manager.beginTransaction().add(R.id.layout, fragment).commit();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return true;
    }
}

Fragment:

public class FirstFragment extends Fragment {

    @Override
    public void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater,  ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_first,container,false);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.fragment_menu, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()){
            case R.id.item:
                Toast.makeText(getActivity(),"选项1",Toast.LENGTH_SHORT).show();
                break;
        }
        return true;
    }
}

Dialog 式的 Fragment

Fragment 有一个特殊的子类 DialogFragment,它可以展示一个对话框式的 Fragment,使用方法也很简单,只需要让我们的类继承 DialogFragment 并且实现其布局,然后在调用的时候,直接调用我们的 Fragment 对象的 show 方法即可,该方法接收两个参数,第一个参数是 FragmentManager 对象,第二个参数是一个 Tag。

public class MainActivity extends Activity {

    private Button bt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        bt = (Button) findViewById(R.id.bt);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DialogFragmentTest fragment = new DialogFragmentTest();
                fragment.show(getFragmentManager(),"dialogFragment");
            }
        });
    }
}
Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1