Android必知必会——Fragment

475 阅读7分钟

概览

Fragment(片段) 表示 FragmentActivity 中的行为或界面的一部分。

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

应将每个片段都设计为可重复使用的模块化 Activity 组件。换言之,由于每个片段都会通过各自的生命周期回调来定义自己的布局和行为,可以将一个片段加入多个 Activity,因此,应采用可复用式设计,避免直接通过某个片段操纵另一个片段。

片段必须始终托管在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。

生命周期

各生命周期阶段

经典的生命周期图示:

  • onAttach(Context context)

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

  • onCreate(Bundle savedInstanceState)

系统会在创建片段时调用此方法。当片段经历暂停或停止状态继而恢复后,如果希望保留此片段的基本组件,则应在具体实现中将其初始化。

  • onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

系统会在片段首次绘制其界面时调用此方法。如要为片段绘制界面,此方法中返回的 View 必须是片段布局的根视图。如果片段未提供界面,可以返回 null。

  • onActivityCreated(Bundle savedInstanceState)

当 Activity 的 onCreate() 方法已返回时进行调用。

  • onStart()、onResume()、onPause()、onStop()

同Activity

  • onDestroyView()

在移除与片段关联的视图层次结构时进行调用。

  • onDestroy()

当片段不再使用时调用。

  • onDetach()

在取消片段与 Activity 的关联时进行调用。

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

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

和 Activity 一样,片段也以三种状态存在:

  • 已恢复 片段在运行中的 Activity 中可见。
  • 已暂停 另一个 Activity 位于前台并具有焦点,但此片段所在的 Activity 仍然可见(前台 Activity 部分透明,或未覆盖整个屏幕)。
  • 已停止 片段不可见。宿主 Activity 已停止,或片段已从 Activity 中移除,但已添加到返回栈。已停止的片段仍处于活动状态(系统会保留所有状态和成员信息)。不过,它对用户不再可见,并随 Activity 的终止而终止。

对于 Activity 生命周期与片段生命周期而言,二者最显著的差异是在其各自返回栈中的存储方式。默认情况下,Activity 停止时会被放入由系统管理的 Activity 返回栈中;不过,只有当在移除片段的事务执行期间通过调用 addToBackStack() 显式请求保存实例时,系统才会将片段放入由宿主 Activity 管理的返回栈。

Fragment创建和管理

创建

继承基类Fragment重写onCreateView提供内容布局。

或者继承Android系统内置的带有特殊属性的Frament:DialogFragment(显示浮动对话框)、ListFragment(显示由适配器(如 SimpleCursorAdapter)管理的一系列项目,类似于 ListActivity)、PreferenceFragmentCompat(以列表形式显示 Preference 对象的层次结构)

Activity中添加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 类。

  • 通过编程方式将片段添加到某个现有 ViewGroup
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment = ExampleFragment()
fragmentTransaction.add(R.id.fragment_container, fragment)
//必须调用commit使操作生效
fragmentTransaction.commit()

唯一标识Fragment

  • id:提供唯一 ID。
  • tag:提供唯一字符串。

管理Fragment

如要管理 Activity 中的片段,需使用 FragmentManager。如要获取它,从 Activity 调用 getSupportFragmentManager()。

可执行的操作包括:

  • 通过findFragmentById()或findFragmentByTag()获取 Activity 中存在的片段。
  • 通过 popBackStack()(模拟用户发出的返回命令)使片段从返回栈中弹出。
  • 通过 addOnBackStackChangedListener() 注册侦听返回栈变化的侦听器。

执行Fragment事务

在 Activity 中使用片段的一大优点是,可以通过片段执行添加、移除、替换以及其他操作,从而响应用户交互。提交给 Activity 的每组更改均称为事务。每个事务都是想要同时执行的一组更改。可以使用 add()、remove() 和 replace() 等方法,为给定事务设置想要执行的所有更改。然后,如要将事务应用到 Activity,必须调用 commit()。

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

val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val newFragment = ExampleFragment()
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, newFragment)
transaction.addToBackStack(null)
transaction.commit()

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

commitAllowingStateLoss()

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

与Activity通信

尽管 Fragment 作为独立于 FragmentActivity 的对象实现,并且可在多个 Activity 内使用,但片段的给定实例会直接绑定到托管该片段的 Activity。

片段可通过 getActivity() 访问 FragmentActivity 实例,并轻松执行在 Activity 布局中查找视图等任务;Activity 也可使用 findFragmentById() 或 findFragmentByTag(),通过从 FragmentManager 获取对 Fragment 的引用来调用片段中的方法。

在某些情况下,可能需使用片段来与 Activity 和/或 Activity 托管的其他片段共享事件或数据。此时,应在片段内定义回调接口,并要求宿主 Activity 实现此接口。

关于Fragment事务的add和replace

在Fragment的切换中有两种方式

  • 使用replace直接切换
  • 使用add先添加Fragment,然后再使用hide隐藏不显示的Fragment,最后再用show将需要显示的Fragment显示出来

调用add添加Fragment时的生命周期

调用replace切换fragment时的生命周期

可以看出,每次replace会把生命周期全部执行一遍,如果在这些生命周期函数里拉取数据的话,就会不断重复的加载刷新数据。

关于add

一般会配合hide使用: transaction.add(R.id.fragment_container, oneFragment).hide(twoFragment).commit();

  • 第一个参数是容器id, 第二个参数是要添加的fragment,添加不会清空容器中的内容,不停的往里面添加。
  • 不允许添加同一个fragment实例,这是非常重要的特点。如果一个fragment已经进来的话,再次添加的话会报异常错误的。
  • 添加进来的fragment都是可见的(visible),后添加的fragment会展示在先添加的fragment上面,在绘制界面的时候会绘制所有可见的view。
  • 所以大多数add都是和hide或者是remove同时使用的。这样可以节省绘制界面的时间,节省内存消耗,是推荐的用法。

关于replace

transaction.replace(R.id.fragment_container, oneFragment).commit();

  • 替换会把容器中的所有内容全都替换掉,有一些app会使用这样的做法,保持只有一个fragment在显示,减少了界面的层级关系。

合理的使用方案

  • 一般情况下,如果Fragment不是很多就可以使用add来进行切换,这样能提高切换时的效率,保证app的流畅性(空间换时间)。
  • 如果Fragment比较多,并且对内存要求较高时,就用replace来进行切换,保证app不会内存溢出(时间换空间)。