Fragment是Android API中的一个类,它代表Activity中的一部分界面;您可以在一个Activity界面中使用多个Fragment,或者在多个Activity中重用某一个Fragment。
本文将介绍Fragment的定义、创建、添加、移除、生命周期 等,如需访问官方原文,您可以点击这个链接:《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的生命周期如下所示:
图1
为了方便起见,继承下面这些特殊的Fragment可以简化其初始化过程:
DialogFragment:可展示一个悬浮的对话框。使用该类创建的对话框可以很好地替换由Activity类中的方法创建的对话框,因为您可以像管理其他Fragment一样管理DialogFragment——它们都被压入由宿主Activity管理的Fragment栈中,这可以很方便的找回已被压入栈中的Fragment。
ListFragment:可以展示一个内置的AdapterView,该AdapterView由一个Adapter管理着,如SimpleCursorAdapter。ListFragment类似于ListActivity,它提供了大量的用于管理List View的方法,比如回调方法onListItemClick(),它用于处理点击项事件。
PreferenceFragment:可以展示层级嵌套的Preference对象列表。PreferenceFragment类似于PreferenceActivity,该类一般用于为应用程序编写设置页面。
必须重写onCreateView()方法,为Fragment绑定布局,该方法返回的View就是Fragment的根视图。
!请注意:若继承的Fragment是ListFragment,onCreateView()
方法已默认返回了ListView对象,故无需再重写该方法。
下面是一个将example_fragment.xml
布局文件绑定至ExampleFragment
的示例:
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);
}
}
方法回传的第二个参数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))。
一般地,Fragment绑定的UI作为宿主Activity的一部分,嵌套在整个Activity层级视图中。将Fragment加入Activity,有如下两种方式:
<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>
其中标签
中的属性android:name
指定Fragment的全限定类名(specifies the Fragment class to instantiate in the layout)。
当系统加载Activity的layout视图时,同时加载Fragment绑定的视图,并回调相应Fragment的onCreateView()
方法,系统将
标签替换为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)。
当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();
有时,为了让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
,它的路径为:
为了在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()
方法注册监听器,用于监听后退栈的变化。
使用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后退栈中。通过点击后退键,用户可以查看该后退栈中的内容。
举例如下:
// 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
对象所绑定的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()
,系统将抛出异常。
尽管一个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);
有些情况下,您需要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()
方法):
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
...
}
若宿主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后退栈的更多内容,您可以参考这个官方文档:《Tasks and Back Stack》。
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的生命周期直接影响其管辖的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控制。