转载自刘明渊 的博客地址:http://blog.csdn.net/vanpersie_9987
Fragment 是 Android API 中的一个类,它代表Activity中的一部分界面;您可以在一个 Activity 界面中使用多个 Fragment,或者在多个 Activity 中重用某一个 Fragment。
本文将介绍 Fragment 的 定义、创建、添加、移除、生命周期 等,如需访问官方原文,您可以点击这个链接(大多数情况下你得借助某些工具才可以访问):
https://developer.android.com/guide/components/fragments.html
Fragments
可以把 Fragment 想象成 Activity 中的一个模块,它有自己的生命周期、可以接收输入事件、可以在 Activity 运行时将 Fragment 动态添加和移除等。
Fragment 必须嵌入在 Activity 中才能生存,其生命周期也直接受宿主 Activity 的生命周期的影响。比如,若宿主 Activity 处于 pause 状态,它所管辖的 Fragment 也将进入 pause 状态。而当 Activity 处于 resume 状态的时候,您可以独立地控制每一个 Fragment,如添加或删除等。
当执行一个 Fragment 事务时,也可以将该 Fragment 加入到一个由宿主 Activity 管辖的后退栈中,并由 Activity 记录加入到后退栈的 Fragment 信息,按下后退键可以将 Fragment 从后退栈中一次弹出。
将 Fragment 添加至 Activity 的视图布局中有两种方式:一种是使用fragment标签加入,Fragment的父视图应是一个ViewGroup;另一种使用代码动态加入,并将一个ViewGroup作为Fragment的容器。
在某些情况下,fragment并不作为Activity视图展示的一部分,它可能只是用来作为非显示性的功能。
设计哲学
Fragment 是 Android 3.0 (API level 11) 新加入的API,主要的设置目的是为了使UI在不同的屏幕上表现得更加灵活。由于平板比手机屏幕大的多,因此平板上可以呈现更多的内容,而 Fragment 可以实现同一视图布局在不同大小的屏幕上显示不同的效果,将 Fragment 加入到 Activity 的 Layout 中,可以在运行时动态替换 Fragment 并将 Fragment 保存至 Activity 管辖的后退栈中。
比如说,一个新闻应用程序运行在平板上时,一个 Activity 视图界面可以装载两个 Fragment,其中左边的 Fragment 用于显示新闻的标题,而右边的 Fragment 用于显示相应的新闻内容;若将该应用运行在手机上,一个 Activity 视图界面无法装载两个 Fragment,故将两个 Fragment 分别装载到两个 Activity中,如下所示:
创建
为了创建Fragment,需要继承一个 Fragment 类,并实现 Fragment 的生命周期回调方法,如 onCreate(), onStart(), onPause(), onStop() 等。事实上,若需要在一个应用中加入 Fragment,只需要将原来的 Activity 替换为 Fragment,并将 Activity 的生命周期回调方法简单地改为 Fragment 的生命周期回调方法即可。
一般来说,在 Fragment 中应至少重写这些生命周期方法:
-
onCreate():当创建 Fragment 实例时,系统回调的方法。在该方法中,需要对一些必要的组件进行初始化,以保证这个组件的实例在 Fragment 处于 pause或stop 状态时仍然存在。
-
onCreateView():当第一次在 Fragment 上绘制UI时,系统回调的方法。该方法返回一个 View 对象,该对象表示 Fragment 的根视图;若 Fragment 不需要展示视图,则该方法可以返回 null。
-
onPause():当用户离开 Fragment 时回调的方法(并不意味着该 Fragment 被销毁)。在该方法中,可以对 Fragment 的数据信息做一些持久化的保存工作,因为用户可能不再返回这个 Fragment。
大多数情况下,需要重写上述三个方法,有时还需要重写其他生命周期方法,Fragment 的生命周期如下所示:
为了方便起见,继承下面这些特殊的Fragment可以简化其初始化过程:
-
DialogFragment:可展示一个悬浮的对话框。使用该类创建的对话框可以很好地替换由 Activity 类中的方法创建的对话框,因为您可以像管理其他 Fragment 一样管理 DialogFragment——它们都被压入由宿主 Activity 管理的 Fragment 栈中,这可以很方便的找回已被压入栈中的 Fragment。
-
ListFragment:可以展示一个内置的 AdapterView,该 AdapterView 由一个 Adapter 管理着,如 SimpleCursorAdapter。ListFragment 类似于 ListActivity,它提供了大量的用于管理 ListView 的方法,比如回调方法 onListItemClick(),它用于处理点击项事件。
-
PreferenceFragment:可以展示层级嵌套的 Preference 对象列表。PreferenceFragment 类似于 PreferenceActivity,该类一般用于为应用程序编写设置页面。
绑定UI布局
必须重写 onCreateView() 方法,为 Fragment 绑定布局,该方法返回的 View 就是 Fragment 的根视图。
!请注意:若继承的 Fragment 是 ListFragment,onCreateView() 方法已默认返回了 ListView 对象,故无需再重写该方法。
下面是一个将 example_fragment.xml 布局文件绑定至 ExampleFragment 的示例:
方法回传的第二个参数 ViewGroup 来自宿主 Activity 容器布局,Fragment 的布局将其作为根视图插入至该视图中(is the parent ViewGroup (from the activity’s layout) in which your fragment layout will be inserted)。第三个参数 Bundle 用于回传之前占据该位置的 Fragment 实例所保存的 Bundle 信息,当该 Fragment 的新实例处于 resume 状态时,该参数被回传(provides data about the previous instance of the fragment, if the fragment is being resumed )。
inflate() 方法需要三个参数:
-
参数1(int):需要绑定的Layout的资源ID;
-
参数2(ViewGroup):绑定的Layout布局的父视图;
-
参数3(boolean):是否需要将参数1的Layout资源依附于(should be attached to)参数2的ViewGroup上,上例中为false,表示不依附。(系统已经默认将Layout插入至ViewGroup中,若为true,将添加一层冗余的视图(redundant view group in the final layout))。
添加至Activity
一般地,Fragment 绑定的UI作为宿主 Activity 的一部分,嵌套在整个 Activity 层级视图中。将 Fragment 加入 Activity ,有如下两种方式:
方式一:使用fragment标签加入
其中标签 <fragment> 中的属性 android:name 指定 Fragment 的全限定类名(specifies the Fragment class to instantiate in the layout)。
当系统加载 Activity 的 layout 视图时,同时加载 Fragment 绑定的视图,并回调相应 Fragment 的 onCreateView() 方法,系统将 <fragment> 标签替换为 onCreateView() 方法返回的 View。
!请注意:必须为fragment设定唯一的身份标识,以便当宿主Activity为restart状态时可以恢复(restore)fragment。
有三种为fragment设置唯一标识的方式:
-
通过 android:id 属性为 fragment 指定唯一ID;
-
通过 android:tag 属性为 fragment 指定唯一字符串标识;
-
若上述两种凡是均未指定,则该 fragment 的标识为其父容器控件的ID(the system uses the ID of the Container view)。
方式二:编写代码将 fragment 动态添加至现存的 ViewGroup(Or, programmatically add the fragment to an existing ViewGroup)
当 Activity 处于 running 状态时,可以将 fragment 添加至 Activity 布局 layout 中,您只需要指定 fragment 的父容器就行了。
为了在 Activity 中对 fragment 做添加、删除、替换等操作(add, remove, or replace a fragment),需调用 FragmentTransaction:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
使用 add() 方法添加 fragment,并指定其添加位置,最后调用 commit() 方法提交事务:
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
添加未绑定UI的Fragment
有时,为了让 fragment 执行一些后台行为(background behavior),可以不为 fragment 绑定UI。
为了给 Activity 添加这种不带UI的 fragment,需调用 add(Fragment, String) 方法,其中第二个参数 String 是为 fragment 指定一个唯一的 tag 标签,而非指定 View的ID(supplying a unique string “tag” for the fragment, rather than a view ID)。由于未绑定UI,故无需重写 onCreateView() 方法。
用 String 标签为未绑定UI的 fragment 指定唯一标识并不严谨(Supplying a string tag for the fragment isn’t strictly for non-UI fragments),您也可以给绑定了UI的 fragment 指定 String 标签;但是若某个 fragment 未绑定UI,那么只能用 String 标签来标识该 fragment,若需要在 Activity 中获取该 fragment,需调用 findFragmentByTag() 方法。
在开发者下载的SDK中,有一个不带UI的fragment的示例程序 FragmentRetainInstance.Java ,它的路径为:
<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java
管理Fragments
为了在 Activity 中管理 Fragment,需调用 getFragmentManager() 方法获取 FragmentManager 实例。您可以使用 FragmentManager 完成如下操作:
-
调用 findFragmentById() 方法获取由 Activity 管辖的绑定了UI的 Fragment 实例(for fragments that provide a UI in the activity layout);调用 findFragmentByTag() 方法获取由 Activity 管辖的未绑定UI的 Fragment 实例(for fragments that do or don’t provide a UI);
-
调用 popBackStack() 方法将 Fragment 从后退栈中弹出;
-
调用 addOnBackStackChangedListener() 方法注册监听器,用于监听后退栈的变化。
执行Fragments事务
使用 Fragment 的最大好处就是可实现动态添加、删除、替换 等 操作,实现与用户的交互。每一组向 Activity 提交的变化称为事务(Each set of changes that you commit to the activity is called a transaction),您可以使用 FragmentTransaction 这个API操作事务。您也可以将事务保存在由 Activity 管辖的后退栈中,以方便用户点击后退键来查看 Fragment 的变化。
使用示例如下:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每个事务都是您希望同时执行的一组变化(Each transaction is a set of changes that you want to perform at the same time.),这些变化包括 add(), remove(), 和 replace() 等操作,最后,为了使事务在Activity中生效,需调用 commit() 方法。
在调用 commit() 方法之前,可以调用 addToBackStack() 方法,将该事物添加到由宿主 Activity 管辖的 Fragment 后退栈中。通过点击后退键,用户可以查看该后退栈中的内容。
举例如下:
在上述示例中,将 newFragment 对象所绑定的UI视图替换至以 R.id.fragment_container 为ID的 layout 容器中,并调用 addToBackStack() 方法将该事物添加至后退栈中。这样,这一事务就被保存到了后退栈中,用户可以找回该事物;另外,点击 back按钮 也可以显示替换之前的 Fragment。
若您在一组事务中进行了不止一项操作(如同时调用了 add()、remove() 等方法),并调用 addToBackStack() 方法将该事务加入后退栈中,那么在调用 commit() 之前,这些操作将作为一个事务整体存在于后退栈中,用户点击 back键 将会整体回退。
使用 FragmentTransaction 操作事务时,操作顺序是没有规定的,但以下两点必须注意:
-
commit() 必须在最后调用;
-
若您希望在一个布局容器中添加多个 Fragment,那么加入的顺序决定了这些 Fragment 绑定的UI视图在 View树 中的层级顺序(the order in which you add them determines the order they appear in the view hierarchy)。
若在事务中进行了 remove 操作,而且在提交事务之前未调用 addToBackStack() 方法,那么该 Fragment 会被 destroy,用户点击back键将无法找回;相反,若调用了 addToBackStack(),那么 Fragment 将处于 stopped 状态,用户点击back键将可以找回。
对于每个 Fragment 事务,您可以在调用 commit() 方法之前调用 setTransition() 方法,为事务的变化添加动画。
调用 commit() 方法并不会立即执行事务,因为执行事务是在UI线程(主线程)中进行的,只有当主线程空闲时,才会执行事务操作(it schedules it to run on the activity’s UI thread (the “main” thread) as soon as the thread is able to do so)。如有必要,可以在UI线程中调用 executePendingTransactions() 方法,以便在 commit() 方法调用后立即执行提交的事务。但一般没必要这么做,除非事务的操作依赖于其他线程(Doing so is usually not necessary unless the transaction is a dependency for jobs in other threads)。
调用 commit() 提交事务的时机应是“Activity保存状态之前”( prior to the activity saving its state),即用户离开 Activity 之前。若试图在这个时机之后调用 commit(),系统将抛出异常。
与Activity交互
尽管一个 Fragment 实例独立于一个 Activity,并且一个 Fragment 可以嵌入到多个 Activity 中(可以重用),但 Activity 包含的 Fragment 将直接有这个 Activity 管辖。
具体来说,Fragment 可以通过 getActivity() 方法获取其宿主 Activity 的对象引用,通过该引用,可以调用 Activity 中的 findViewById() 方法获得布局中的视图控件:
View listView = getActivity().findViewById(R.id.list);
类似地,也可以在 Activity 中获取 Fragment 的实例:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
为Activity创建事件回调
有些情况下,您需要 Fragment 响应 Activity 的事件,好的做法是在 Fragment中添加回调接口,并在其宿主 Activity 中实现。当 Activity 通过接口接收了回调,它可以与其他 Fragment 共享信息(When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary)。
举例来说,一个新闻应用的 Activity 包含两个 Fragment,其中 Fragment A 用于显示新闻标题,而 Fragment B 用于显示新闻内容。Fragment A 必须告诉Activity 它的列表项何时被点击,这样 Activity 可以控制 Fragment B 显示新闻内容,所以必须在 Fragment A 中定义一个事件监听接口 OnArticleSelectedListener:
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
接着,需要在 Activity 中实现 OnArticleSelectedListener 接口,并重写 onArticleSelected() 方法,并通知 Fragment B 来自于 Fragment A 的点击事件。为了保证宿主 Activity 实现该接口,需在 Fragment A 中的回调方法 onAttach() 中做如下工作(当 Fragment 添加至 Activity 时,系统回调 onAttach() 方法):
若宿主 Activity 未实现 OnArticleSelectedListener 接口,Fragment 将抛出 ClassCastException 异常。正常情况下,Fragment A 的成员变量 mListener 持有实现了 OnArticleSelectedListener 接口的对象引用,所以,Fragment A 可以通过宿主 Activity 传递点击事件。具体来说,当用户点击了某个列表项时,Fragment A 中的 onListItemClick() 方法被回调,在该方法中调用 OnArticleSelectedListener 接口中的 onArticleSelected() 方法(程序执行的实际上是重写的 onArticleSelected() 方法)。
创建菜单项
在 Fragment 中回调 onCreateOptionsMenu() 方法可以创建菜单项,为了保证这个方法能够被系统回调,您必须在 onCreate() 生命周期方法中调用 setHasOptionsMenu() 方法。
当 menu 项被点击时,Fragment 也像 Activity 一样,通过 onOptionsItemSelected() 回调方法响应 menu 项的点击事件。
处理生命周期
与Activity类似,Fragment也同样有三种状态:
-
Resumed:在一个处于 running 状态的 Activity 中,Fragment 处于可见状态(The fragment is visible in the running activity);
-
Paused:另一个 Activity 处于前台并获得了焦点,该 Fragment 的宿主 Activity 并未被全部遮挡;
-
Stopped:Fragment 不可见。可能由于宿主 Activity 已不可见或 Fragment已被 Activity 移除(但加入到了Fragment后退栈中),一个处于 stopped 状态的 Fragment 仍是 alive 的,但当宿主 Activity 被 kill 时,该 Fragment也 将被 kill。
与 Activity 类似,您同样可以使用 Bundle 对象保存 fragment 的状态信息,以防宿主 Activity 所在进程被 kill、而迫使 Activity 重新创建、接着 Fragment 才能重新创建、这时 Fragment 的状态信息将丢失。
重写 Fragment 的 onSaveInstanceState() 回调方法,并在回传的 Bundle 参数中保存状态信息;接着在 onCreate(), onCreateView(), onActivityCreated() 方法中获得保存的状态信息。
Activity 与 Fragment 生命周期的一个最大区别就是它们的实例是如何存储在各自的后退栈中的( how one is stored in its respective back stack):
-
在默认情况下,Activity 实例会在 stop 状态时被压入由系统管理的 Activity 后退栈中(An activity is placed into a back stack of activities that’s managed by the system when it’s stopped, by default),所以用户可以通过点击back按钮恢复已入栈的 Activity 实例。
-
对于fragment,只能在操作事务时显式地调用 addToBackStack() 方法将 fragment 压入由宿主Activity 管理的 fragment 后退栈。
除此之外,Fragment 与 Activity 的生命周期非常相似,您只需了解 Activity 的生命周期是如何影响 Fragment 的生命周期就行了。
!请注意:如需在 Fragment 中使用 Context 对象,您可以调用 getActivity() 方法,但这个方法只能是在 Fragment 已经依附于 Activity 后才能调用( to call getActivity() only when the fragment is attached to an activity)。当 Fragment 未依附于某个 Activity 或 Fragment 已经处于其生命周期的末尾而不再依附于某个 Activity 时,调用 getActivity() 方法会直接返回 null。
依附Activity生命周期
宿主 Activity 的生命周期直接影响其管辖的 fragment 的生命周期,Activity 的每一个生命周期方法被回调后,其管辖的 fragment 的相应生命周期方法会跟着回调。如当 Activity 回调 onPause() 时,fragment 也会回调 onPause()。
fragment的其他生命周期方法如下:
-
onAttach():当 fragment 实例依附于 Activity 时被回调,Activity 对象的引用回传到该方法中(the Activity is passed in here);
-
onCreateView():为 fragment 绑定UI视图时,该方法被回调;
-
onActivityCreated():当宿主 Activity 的 onCreate() 方法返回后,该方法被回调;
-
onDestroyView():当与 fragment 绑定的UI视图被移除时,该方法被回调;
-
onDetach():当 fragment 不再依附于 Activity 时,该方法被回调;
除了上述方法外,其他的 fragment 生命周期方法均由其宿主 Activity 的生命周期直接影响。有些 Activity 的生命周期方法直接影响了多个 fragment 的生命周期方法,比如说,当 Activity 的 onCreate() 被回调时,将导致 fragment 的 onAttach()、onCreate()、onCreateView()、onActivityCreate() 被连续回调( you can see how each successive state of the activity determines which callback methods a fragment may receive)。如上图所示。
一旦 Activity 处于 resume 状态时,您可以自由地添加或移除 fragment,也就是说,只有当 Activity 的状态为 resume 时,fragment 才能够自由地控制自己的生命周期。
当 Activity 不在 resume 状态时,fragment 的生命周期将由宿主 Activity 控制。