本文翻译自android官方文档,结合自己测试,整理如下。
Fragment在activity中表示一个行为或者UI的一部分。我们可以在一个activity中使用多个fragments或者在多个activities复用一个fragment。我们可以把fragment作为activity模块化的一部分,fragment有自己的生命周期,UI布局。我们可以在activity运行时动态添加或删除fragment。
fragment必须要嵌入到activity中,fragment的生命周期受到宿主activity的影响。例如,当activity处于暂停状态时,在activity中的所有fragments也进入暂停状态,当activity销毁时,activity中所有的fragments也全部销毁。但是,当activity处于运行状态时,我们可以单独处理每个fragment,例如我们可以添加或者删除它们。当我们执行完一个fragment事务时,我们也可以将fragment添加到Back栈中,该Back栈由宿主activity管理,栈中的实体记录的是fragment发生的事务操作。有了Back栈,我们就可以通过按BACK键回到fragment事务发生之前的状态。
当我们向activity布局中添加fragment时,它保存在ViewGroup中,该ViewGroup将添加到activity视图的层次结构中,fragment可以定义自己的布局文件。可以在布局文件中通过<fragment>
标签添加fragment,或者通过代码添加到存在的ViewGroup中。但是fragment不是必须作为activity布局的一部分;我们仍然可以使用没有布局的fragment处理activity不用显示的任务。
下面将具体介绍fragment的详细用法。
Android从3.0(API11)开始引入fragments,主要是为了在大屏幕上支持更加动态和灵活的UI设计,例如平板。因为平板的屏幕远大于手持设备(手机等),有更大的空间来存放和交互UI组件。Fragments能够自动处理view层次变化的设计,不用我们来管理。通过将activity布局分成fragments,我们能够在activity运行时改变activity的呈现状态,并且可以在Back栈中管理这些改变,该Back栈由宿主activity管理。
例如,新闻应用程序可以在左边使用一个fragment显示标题列表,而在右边使用另一个fragment用于显示具体新闻内容。这两个fragments都在同一个activity中,一左一右,每个fragment有自己的生命周期方法并且处理自己的输入事件。这样的话我们可以避免使用两个activities达到这种效果。如下图所示:
我们应该把fragment设计成模块化可重用的activity组件。这是因为每个fragment可以定义自己的布局,有自己的生命周期,我们可以在多个activities中使用同一个fragment,因此我们应该设计成可重用的避免在fragment中直接操作另一个fragment。模块化的fragment可以允许我们根据不同的屏幕大小改变fragments之间的组合。当设计程序同时支持平板和手机时,我们可以在不同的布局配置中重用我们的fragments,以此来适应不同的屏幕空间,提高用户体检质量。例如上面的新闻客户端的例子。对于大屏的平板来说可以在一个activity中同时显示新闻标题和新闻内容这两个fragment,而对于手机来说,一个fragment只能放在一个activity中。
为了创建一个fragment,我们必须创建一个Fragment类(或者它的子类)的子类。Fragment中的生命周期方法和Activity有很多相同的地方。例如fragment也包含:onCreate()
,onStart()
,onPause()
,onStop()
等。实际上,若我们想要把现有的应用程序使用fragment的话,我们只需要将activity的回调方法中的代码移到对应的fragment的回调方法中即可。
通常情况下,我们至少要实现一下方法:
-onCreate()
当创建fragment时系统会调用该方法。我们应该在这里初始化必要的组件,这些组件当fragment进入暂停或停止之后重新启动后仍被保留。
- onCreateView()
当首次为frgment准备UI布局时,系统调用该方法。为了给fragment绘画UI布局,该方法必须返回一个View对象作为fragment布局的根。若fragment不提供布局的话,该方法返回null。
- onPause()
当用户将要离开该fragment时(离开但不一定销毁该fragment),系统调用该方法。通常我们需要在这里保存那些需要永久保存的信息,这是因为用户可能不在回来。
关于生命周期中其它的方法我们将在后续处理fragment生命周期中详细介绍。
除了基类Fragment之外,系统还有我们提供了几个继承Fragment的子类:
fragment通常被作为activity UI的一部分,fragment将自己的布局贡献给activity。
为了提供fragment的布局,我们必须实现onCreateView()
,该方法会在系统开始创建该fragment的布局时由系统调用。同时该方法必须返回一个View作为我们frament的布局的根视图。
注意:若我们的fragment是ListFragment的子类,则该方法默认会返回一个ListView,因此不用我们来实现该方法。
为了从onCreateView()
返回一个布局,我们利用xml中定义的布局文件,为了达到这个目的,在onCreateView()
提供了LayoutInflater对象参数。例如:
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布局需要插入到该布局中。Bundle对象用于保存之前运行的fragment状态,若之前不存在则返回null。
LayoutInflater对象的inflate()
方法传递三个参数:
通过以上介绍我们知道了如何创建一个带有布局的fragment,下面,我们介绍如何将fragment添加到activity中。
通常,fragment作为宿主activity布局的一部分,有两种方法将fragment添加到activity中:
静态加载
该方法通过在activity布局文件中声明fragment控件。这种情况下我们可以像指定视图一样指定fragment的布局属性。例如下面声明了带有两个fragments的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类。
当系统创建activity布局时,它实例化在activity布局中指定的每个fragment,通过onCreateView()
检索相应的布局,并将这些布局插入到对应<fragement>
标签所在的位置。
注意:每个fragment需要提供一个唯一标识符,通过该标识符系统可以保存这些fragment(在重新启动activity时)。有下列三种方式指定:
1. 在<fragment>
标签中通过android:id
指定一个ID;
2. 在<fragment>
标签中通过android:tag
指定一个字符串;
3. 若以上两种都不指定的话,系统使用父控件(inflate()
方法的第二个参数)的ID。
动态加载
该方法是指将fragment动态地添加到一个存在的ViewGroup中。在activity运行的任何时候,我们能够将fragment添加到activity中。我们只需要指定一个被fragment替换的ViewGroup对象即可。
若想在activity中进行fragment事务操作(事务是指原子操作,一次事务要么都执行要么都不执行。fragment事务操作如:添加,删除,替换fragment),我们能够使用FragmentTransaction类,可以在activity如下操作:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
我们可以使用add()
方法添加fragment,add()
方法接收两个参数,分别为:插入到的父布局和要添加的fragment对象。如下:
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
每次使用FragmentTransaction对象执行事务之后都需要调用commit()
提交事务执行。
上面介绍了如何通过fragment向activity中提供UI布局。然而,我们也可以使用不带布局的fragment,该fragment可以提供后台服务。
为了添加无UI布局的fragment,我们可以在activity中使用add(Fragment,String)
(String对象为fragment的tag属性)添加fragment。由于该fragment不需要UI布局(不与activity布局发生关联),因此我们可以不用实现onCreateView()
。
使用tag字符串不是严格意义上的无布局fragment,我们也可以为有布局的fragment提供tag字符串。但是若fragment没有相关布局的话,那么tag字符串是唯一标识该fragment的方法。若想在之后使用该fragment的话,可以通过findFragmentByTag()
。
关于后台fragment使用,可以参见之前的内容:在配置改变时保留一个对象用于恢复数据。
为了在activity中管理fragments,我们可以使用FragmentManager对象,可以在activity中通过`getFragmentManager()获取该对象。
我们可以通过FragmentManager完成以下事宜:
findFragmentById()
(获取有UI布局的fragments)或findFragmentByTag()
(获得有/无UI布局的fragments)获取activity中存在的fragments。popBackStack()
将fragment从Back栈中弹出(模拟用户BACK命令)。addOnBackStackChangedListener()
为Back栈注册事件监听。关于FragmentManager类更多内容可以参考官方文档中的介绍。
上一小节介绍了可以通过FragmentTransaction开启一个事务操作,下面详细介绍。
通过Fragment事务,我们能够完成添加/删除/替换操作。每次操作的集合都可称为一次事务。我们也可以通过Back栈对事务进行管理,可以通过activity管理Back栈,该Back栈允许用户通过导航回到变化之前的状态(类似activity的Back栈)。
事务由一组同一时刻的操作组成。我们可以使用add()
,remove()
,replace()
中的1个或多个完成一次事务操作。最后需要调用commit()
方法完成事务提交。
在调用commit()
之前,我们可能需要调用addToBackStack(String)
(参数为Back栈名),该方法能够实现将操作的事务添加到fragment事务的Back栈中。该Back栈由activity管理,通过BACK键可以回到改变之前的fragment状态。
例如:
// 创建fragment和事务
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// 使用fragment替换id为fragment_container的视图
// 将本次事务添加到back栈中
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// 提交事务
transaction.commit();
例子中,使用ExampleFragment对象替换 R.id.fragment_container。然后调用addToBackStack()
,将替换事务保存到Back栈中。用户可以通过BACK键返回到替换之前的状态。
若在一次事务中执行了多次操作,然后再调用addToBackStack()
,则所有在commit()
之前的操作都会保存在Back栈中,并作为一次事务。
需要注意一下两点:
commit()
;当我们移除一个fragment时,若不调用addToBackStack()
,则当移除事务执行时,该fragment将会销毁,用户无法返回移除前的状态。而若调用的话,当移除一个fragment时,该fragment进入停止状态,若用户通过导航返回时,该fragment又会重新启动。
tip:
setTransaction()
设置事务的动画,该方法在commit()
之前调用。hide(Fragment)
隐藏当前的Fragment,仅仅是设为不可见,并不会销毁;调用show(Fragment)
显示之前隐藏的Fragment。当然这两个操作也是事务,须在调用commit()
之前执行。调用commit()
不会立刻执行事务,而是将该事务添加到主线程的任务表中,一旦主线程能够处理时就会执行该事务。我们可以通过调用FragmentManager的executePendingTransactions()
方法立即执行commit()
方法提交的事务,该方法必须在主线程中执行。通常来说,没有必要这样做,只有在其它线程需要依赖该事务时,才这样做。
注意:commit()
方法需要在activity保存状态之前调用,否则的话会抛出异常。这是因为若activity恢复数据的话,在该方法提交之后的状态可能丢失。若允许丢失数据的话,我们可以调用commitAllowingStateLoss()
而不是调用commit()
。
尽管fragment作为一个独立于activity的对象,并且能够在多个activities中使用同一个fragment,但是我们仍然可以在activity中直接和它包含的fragments进行通信。
在fragment中可以通过getActivity()
方法获取activity,然后就可以进行通信,例如获得activity布局中的控件:
View listView = getActivity().findViewById(R.id.list);
同样,activity可以通过FragmentManager使用findFragmentById()
或者findFragmentByTag()
获取fragment,例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager()
.findFragmentById(R.id.example_fragment);
fragment中有两个方法可以设置和获取保存的Bundle对象:setArgument()
和getArgument()
。
有些情况下,fragment可能需要和宿主activity共享事件。一个好的办法是在fragment中定义一个回调接口,而宿主activity实现该接口。当activity通过接口接收回调时,若有必要的话,他就能够和其它fragments共享信息。
例如新闻应用程序,它包含两个fragments,一个用于显示文章标题列表(fragment A),另一个用于显示文章内容(fragment B)。则当点击A中标题列表的某个文章标题时,就需要在B中显示该文章的具体内容。因此则需要在A中定义一个OnArticleSelectedListener
接口:
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
然后,宿主activity需要实现该接口,并实现onArticleSelected(Uri articleUri)
,该方法用于通知B显示具体内容事件。为了确保在宿主activity中实现该接口,可以在fragment中的onAttach()
中强制转换宿主activity来实例化接口。例如:
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没有实现该接口的话会抛出ClassCastException异常。若实现的话,则mListener变量就持有宿主activity的引用,则A就可以通过接口方法实现共享点击事件。例如,假设A继承自ListFragment,每次用户点击列表选项时,都会调用onListItemClick()
,我们可以在这里面回调接口方法:
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Append the clicked item‘s row ID with the content provider Uri
Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
// Send the event and Uri to the host activity
mListener.onArticleSelected(noteUri);
}
...
}
onListItemClick()
中传递的id参数为被点击的选项的行号(来自ContentProvider的内容)。
关于ContentProviders可以参考我之前翻译的文章:
解读Android之ContentProvider(1)CRUD操作和解读Android之ContentProvider(2)创建自己的Provider。
fragments可以作为activity的Options菜单(因此也是ActionBar)中的菜单选项,通过实现fragment的onCreateOptionsMenu()
。想要回调该方法,必须在onCreate()
中调用setHasOptionsMenu()
,表明该fragment作为菜单选项。
任何通过fragments添加的选项都会附加在已存在的选项菜单上。当点击某个选项时会回调onOptionsItemSelected()
。
我们也可以通过registerForContextMenu()
在fragment布局中注册一个视图提供context菜单。当用户打开context菜单时,就会调用onCreateContextMenu()
。当用户选择某个选项时,就会调用onContextItemSelected()
。
注意:尽管当用户选择某个选项时,fragment接收点击选项回调方法,但是activity会首先回调相应的方法。若activity中没有实现处理选中的选项,则事件会传递给fragment的回调方法。对于context菜单和Options菜单都是如此。
fragment的生命周期类似于activity的生命周期,也存在以下三种状态:
同activity一样,我们也可以在宿主activity所在的进程被杀死时,通过Bundle对象保存fragment状态,然后再activity重建时恢复fragment状态。我们可以在fragment的onSaveInstanceState()
中保存状态,在onCreate()
,或onCreateView()
,或onActivityCreated()
中恢复数据。
关于activity和fragment生命周期一个重要的区别在于如何在Back栈中进行保存。默认情况下,activity被放在activities的Back栈中,Back栈由系统管理。然而,fragment的Back栈由宿主activity管理,并且当fragment被移除时只有在我们通过addToBackStack()
明确将fragment添加到Back栈才行。也就是说fragment若要进入Back栈必须满足两个条件:
1. 该fragment将要被移除;
2. 在commit()
之前必须调用addToBackStack()
。
fragment的生命周期和activity的生命周期类似,我们需要特别注意的是activity的生命周期如何影响fragment的生命周期。
注意:若我们需要在fragment获得一个Context对象,可以通过getActivity()
。然而只有当fragment关联acitivity时才能使用该方法。当fragment还没有关联activity或者解除关联时,该方法返回null。
宿主activity的生命周期直接影响fragment的生命周期,因此,activity的每个周期方法都会导致一个类似的fragment周期方法。
除此之外,fragment还有一些其他的周期方法,如下:
onAttach()
onCreateView()
onActivityCreated()
onCreate()
返回时调用。onDestroyView()
onDetach()
下图描述了宿主activity如何影响fragment:
只有当宿主activity处于运行状态时,我们才能独立处理fragment生命周期了。否则,都会按照上图所示,受到宿主activity的影响。
原文地址:http://blog.csdn.net/wangyongge85/article/details/48084685