Fragment 的基础知识介绍 1.1 概述
1.1.1 特性
By hebang32624
Fragment 是 activity 的界面中的一部分或一种行为。可以把多个 Fragment 组合到一个 activity 中来创建一 个多面界面并且可以在多个 activity 中重用一个 Fragment。可以把 Fragment 认为模块化的一段 activity,它具 有自己的生命周期,接收它自己的事件,并可以在 activity 运行时被添加或删除。
Fragment 不能独立存在,它必须嵌入到 activity 中,而且 Fragment 的生命周期直接受所在的 activity 的影 响。例如:当 activity 暂停时,它拥有的所有的 Fragment 都暂停了,当 activity 销毁时,它拥有的所有 Fragment 都被销毁。然而,当 activity 运行时(在 onResume()之后,onPause()之前),可以单独地操作每个 Fragment, 比如添加或删除它们。当在执行上述针对 Fragment 的事务时,可以将事务添加到一个栈中,这个栈被 activity 管 理,栈中的每一条都是一个 Fragment 的一次事务。有了这个栈,就可以反向执行 Fragment 的事务,这样就可以在 Fragment 级支持“返回”键(向后导航)。
当向 activity 中添加一个 Fragment 时,它须置于 ViewGroup 控件中,并且需定义 Fragment 自己的界面。可 以在 layoutxml 文件中声明 Fragment,元素为:<fragment>;也可以在代码中创建 Fragment,然后把它加入到 ViewGroup 控件中。然而,Fragment 不一定非要放在 activity 的界面中,它可以隐藏在后台为 actvitiy 工作。
1.1.2 生命周期
onCreate():
当创建 fragment 时系统调用此方法。在其中必须初始化 fragment 的基础组件们。可参考 activity 的说明。 onCreateView():
系统在 fragment 要画自己的界面时调用(在真正显示之前)此方法。这个方法必须返回 frament 的 layout 的根控 件。如果这个 fragment 不提供界面,那它应返回 null。
onPause():
大多数程序应最少对 fragment 实现这三个方法。当然还有其它几个回调方法可应该按情况实现之。所有的生命周 期回调函数在“操控 fragment 的生命周期”一节中有详细讨论。
下图为 fragment 的生命周期(它所在的 activity 处于运行状态)。
添加Fragments
onAttach()
onCreate()
onCreateView()
onActivityCreated()
onStart()
onResume()
Fragments是活动的(正在使 用)
Fragment从
返回堆栈中 返回到布局文件
用户使用返 回功能或 Fragments 被移除 (替换)
Fragments被
添加到返回 堆栈中,接着 被移除(替换)
onPause()
onStop
onDestroyView()
onDestroy()
onDetach()
Fragments被销毁
图 1 Fragment 生命周期
1.1.3 派生类
DialogFragment
显示一个浮动的对话框。使用这个类创建对话框是替代 activity 创建对话框的最佳选择.因为可以把 fragmentdialog 放入到 activity 的返回栈中,使用户能再返回到这个对话框。
ListFragment
显示一个列表控件,就像 ListActivity 类,它提供了很多管理列表的方法,比如 onListItemClick()方法响应 click 事件。 PreferenceFragment
显示一个由 Preference 对象组成的列表,与 PreferenceActivity 相同。它用于为程序创建“设置”activity。
1.2 范例
写一个读新闻的程序,可以用一个 fragment 显示标题列表,另一个 fragment 显示选中标题的内容,这两个 fragment 都在一个 activity 上,并排显示。那么这两个 fragment 都有自己的生命周期并响应自己感兴趣的事件。于 是,不需再像手机上那样用一个 activity 显示标题列表,用另一个 activity 显示新闻内容;现在可以把两者放在一个 activity 上同时显示出来。如下图:
图 2 Fragment 说明性示例
Fragment 必须被写成可重用的模块。因为 fragment 有自己的 layout,自己进行事件响应,拥有自己的生命周期
和行为,所以可以在多个 activity 中包含同一个 Fragment 的不同实例。这对于让界面在不同的屏幕尺寸下都能给用 户完美的体验尤其重要。比如可以在程序运行于大屏幕中时启动包含很多 fragment 的 activity,而在运行于小屏幕 时启动一个包含少量 fragment 的 activity。
刚才读新闻的程序,当检测到程序运行于大屏幕时,启动 activityA,将标题列表和新闻内容这两个 fragment 都 放在 activityA 中;当检测到程序运行于小屏幕时,还是启动 activityA,但此时 A 中只有标题列表 fragment,当选中 一个标题时,activityA 启动 activityB,B 中含有新闻内容 fragment。
1.3 创建 Fragmet
要创建 fragment,必须从 Fragment 或 Fragment 的派生类派生出一个类。Fragment 的代码写起来有些像 activity。它 具有跟 activity 一样的回调方法,比如 onCreate(),onStart(),onPause()和 onStop()。实际上,如果想把老的程序改为使 用 fragment,基本上只需要把 activity 的回调方法的代码移到 fragment 中对应的方法即可。
1.3.1 添加有界面的 Fragment
Fragment 一般作为 activity 的用户界面的一部分,把它自己的 layout 嵌入到 activity 的 layout 中。一个要为 fragment 提供 layout,必须实现 onCreateView()回调方法,然后在这个方法中返回一个 View 对象,这个对象是 fragment 的 layout 的根。
注意:如果的 fragment 是从 ListFragment 中派生的,就不需要实现 onCreateView()方法了,因为默认的实现已 经为返回了 ListView 控件对象。
要从 onCreateView()方法中返回 layout 对象,可以从 layoutxml 中生成 layout 对象。为了帮助这样做, onCreateView()提供了一个 LayoutInflater 对象。举例:以下代码展示了一个 Fragment 的子类如何从 layoutxml 文件 example_fragment.xml 中生成对象。
PublicstaticclassExamp leFragmentextendsFragment{ @Override
P ublicV iew onCreat e View (L ay out Inflat erinflat er,View G roup cont ainer, BundlesavedInstanceState){
//Inflate the layout for this fragment
ret urninflat er.inflat e(R.l ay out .examp le_fra gm ent ,cont ainer,false) ; }
}
onCreateView()参数中的 container 是存放 fragment 的 layout 的 ViewGroup 对象。savedInstanceState
参数是一个Bundle,跟 activity的onCreate()中 Bundle差不多,用于状态恢复。但是 fragment的onCreate() 中也有 Bundle 参数,所以此处的 Bundle 中存放的数据与 onCreate()中存放的数据还是不同的。
Inflate()方法有三个参数:
layout 的资源 ID。
存放 fragment 的 layout 的 ViewGroup。
布尔型数据表示是否在创建 fragment 的 layout 期间,把 layout 附加到 container 上(在这个例子
中,因为系统已经把 layout 插入到 container 中了,所以值为 false,如果为 true 会导至在最终的 layout 中创建多余的 ViewGroup)。
下面讲述如何把它添加到 activity 中。把 fragment 添加到 activity 一般情况下,fragment 把它的 layout 作为 activitiy 的 loyout 的一部分合并到 activity 中,有两种方法将一个 fragment 添加到 activity 中:
方法一:在 activity 的 layoutxml 文件中声明 fragment 如下代码,一个 activity 中包含两个 fragment:
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLay outxmlns:Android="http ://schemas.Android.co m/ap k/res/Android"
Android:orientation="horizontal" Android:layout_width="match_parent" Android:layout_height="match_parent">
<fragm ent Android:name="co m.e xa mp le.news.Art icleList Fragment "
Android:id="@+id/list" Android:layout_weight="1" Android:layout_width="0dp"
Android:layout_height="match_parent"/>
<fragm ent Android:name="co m.e xa mp le.news.Art icleReaderFra gment "
Android:id="@+id/viewer" Android:layout_weight="2" Android:layout_width="0dp"
Android:layout_height="match_parent"/> </LinearLayout>
以上代码中,<fragment>中声明一个 fragment。当系统创建上例中的 layout 时,它实例化每一个 fragment,然 后调用它们的 onCreateView()方法,以获取每个 fragment 的 layout。系统把 fragment 返回的 view 对象插入到<fragment> 元素的位置,直接代替<fragment>元素。
注:每个 fragment 都需要提供一个 ID,系统在 activity 重新创建时用它来恢复 fragment,也可以用它来操作 fragment 进行其它的事物,比如删除它。有三种方法给 fragment 提供 ID:
为 Android:id 属性赋一个数字。
为 Android:tag 属性赋一个字符串。
如果没有使用上述任何一种方法,系统将使用 fragment 的容器的 ID。
方法二:在代码中添加 fragment 到一个 ViewGroup
这种方法可以在运行时,把 fragment 添加到 activity 的 layout 中。只需指定一个要包含 fragment 的 ViewGroup。
为了完成 fragment 的事务(比如添加,删除,替换等),必须使用 FragmentTransaction 的方法。
取到 FragmentTransaction,如下:
FragmentManagerfragmentManager =getFragmentManager() FragmentTransactionfragmentTransaction=fragmentManager.beginTransaction();
然后可以用 add()方法添加一个 fragment,它有参数用于指定容纳 fragment 的 ViewGroup。如,Add()的第一个 参数是容器 ViewGroup,第二个是要添加的 fragment。一旦通过 FragmentTransaction 对 fragment 做出了改变,必须 调用方法 commit()提交这些改变。不仅在无界面的 fragment 中,在有界面的 fragment 中也可以使用 tag 来作为为一 标志,这样在需要获取 fragment 对象时,要调用 findFragmentTag()。
1.3.2 添加没有界面的 Fragment
上面演示了如何添加 fragment 来提供界面,然而,也可以使用 fragment 为 activity 提供后台的行为而不用显示 fragment 的界面。要添加一个没有界面的 fragment,需在 activity 中调用方法 add(Fragment,String)(它支持用一个唯 一的字符串做为 fragment 的“tag”,而不是 viewID)。这样添加的 fragment 由于没有界面,所以在实现它时不需 调用实现 onCreateView()方法。
使用 tag 字符串来标识一个 fragment 并不是只能用于没有界面的 fragment 上,也可以把它用于有界面的 fragment 上,但是,如果一个 fragment 没有界面,tag 字符串将成为它唯一的选择。获取以 tag 标识的 fragment,需使用方法 findFragmentByTab()。
1.4 Frament 管理
要管理 fragment,需使用 FragmentManager,要获取它,需在 activity 中调用方法 getFragmentManager()。 可以用 FragmentManager 来做以上事情:
使用方法 findFragmentById()或 findFragmentByTag(),获取 activity 中已存在的 fragment
使用方法 popBackStack()从 activity 的后退栈中弹出 fragment(这可以模拟后退键引发的动作)
用方法 addOnBackStackChangedListerner()注册一个侦听器以监视后退栈的变化
还可以使用 FragmentManager 打开一个 FragmentTransaction 来执行 fragment 的事务,比如添加或删除 fragment。
在 activity 中使用 fragment 的一个伟大的好处是能跟据用户的输入对 fragment 进行添加、删除、替换以及执行 其它动作的能力。提交的一组 fragment 的变化叫做一个事务。事务通过 FragmentTransaction 来执行。还可以把每个 事务保存在 activity 的后退栈中,这样就可以让用户在 fragment 变化之间导航(跟在 activity 之间导航一样)。
可以通过 FragmentManager 来取得 FragmentTransaction 的实例,如下:
FragmentManagerfragmentManager = getFragmentManager();
FragmentTransactionfragmentTransaction =fragmentManager.beginTransaction(); 一个事务是在同一时刻执行的一组动作(很像数据库中的事务)。可以用 add(),remove(),replace()等方法构成事
务,最后使用 commit()方法提交事务。在调用 commint()之前,可以用 addToBackStack()把事务添加到一个后退栈中, 这个后退栈属于所在的 activity。有了它,就可以在用户按下返回键时,返回到 fragment 执行事务之前的状态。如 下例:演示了如何用一个 fragment 代替另一个 fragment,同时在后退栈中保存被代替的 fragment 的状态。
//Create new fragment and transaction
Fragment newFragment = newExampleFragment();
FragmentTransaction transaction=getFragmentManager().beginTransaction();
//Replace whatever is in the fragment_container view with thisfragment, //and add the transaction to the backstack
t ransact ion.rep lace(R.id.fra gm ent _cont ainer,new Fra gment );
transaction.addToBackStack(null) ;
//Commit the transaction transaction.commit();
解释:newFragment 代替了控件 IDR.id.fragment_container 所指向的 ViewGroup 中所含的任何 fragment。然后调 用 addToBackStack(),此时被代替的 fragment 就被放入后退栈中,于是当用户按下返回键时,事务发生回溯,原先 的 fragment 又回来了。
如果向事务添加了多个动作,比如多次调用了 add(),remove()等之后又调用了 addToBackStack()方法,那么所有 的在 commit()之前调用的方法都被作为一个事务。当用户按返回键时,所有的动作都被反向执行(事务回溯)。
事务中动作的执行顺序可随意,但要注意以下两点:
必须最后调用 commit()
如果添加了多个 fragment,那么它们的显示顺序跟添加顺序一至(后显示的覆盖前面的) 如果在执行的事务中有删除 fragment 的动作,而且没有调用 addToBackStack(),那么当事务提交时,那些被删
除的 fragment 就被销毁了。反之,那些 fragment 就不会被销毁,而是处于停止状态。当用户返回时,它们会被恢复。 但是,调用 commit()后,事务并不会马上执行。它会在 activity 的 UI 线程(其实就是主线程)中等待直到线程 能执行的时候才执行(废话)。如果必要,可以在 UI 线程中调用 executePendingTransactions()方法来立即执行事务。
但一般不需这样做,除非有其它线程在等待事务的执行。
注意:只能在 activity 处于可保存状态的状态时,比如 running 中,onPause()方法和 onStop()方法中提交事务, 否则会引发异常。这是因为 fragment 的状态会丢失。如果要在可能丢失状态的情况下提交事务,请使用 commitAllowingStateLoss()。
1.5 Fragment 与 Activity 通讯
尽管 fragment 的实现是独立于 activity 的,可以被用于多个 activity,但是每个 activity 所包含的是同一个 fragment 的不同的实例。Fragment 可以调用 getActivity()方法很容易的得到它所在的 activity 的对象,然后就可以查找 activity 中的控件们(findViewById())。例如:
ViewlistView =getActivity().findViewById(R.id.list);同样的,activity 也可以通过 FragmentManager 的方 法查找它所包含的 frament 们。
例如:
Examp leFra gment
fragment =(ExampleFragment)getFragmentManager().findFragmentById(R.id.example_fragment )
有时,可能需要 fragment 与 activity 共享事件。一个好办法是在 fragment 中定义一个回调接口,然后在 activity 中实现之。例如,还是那个新闻程序的例子,它有一个 activity,activity 中含有两个 fragment。fragmentA 显示新闻标题,fragmentB 显示标题对应的内容。fragmentA 必须在用户选择了某个标题时告诉 activity,然后 activity 再告诉 fragmentB,fragmentB 就显示出对应的内容。
如下例,OnArticleSelectedListener 接口在 fragmentA 中定义:
public static class FragmentA extends ListFragment{ //Container Activity must implement this interface
public interface OnArticleSelectedListener{
public void onArticleSelected(Uri articleUri);
}
然后 activity 实现接口 OnArticleSelectedListener,在方法 onArticleSelected()中通知 fragmentB。当 fragment 添加到 activity 中时,会调用 fragment 的方法 onAttach(),这个方法中适合检查 activity 是否实现了
OnArticleSelectedListener 接口,检查方法就是对传入的 activity 的实例进行类型转换,如下所示:
public static class FragmentA extends ListFragment{ OnArticleSelectedListenermListener;
...
@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 没有实现那个接口,fragment 抛出 ClassCastException 异常。如果成功了,mListener 成员变 量保存 OnArticleSelectedListener 的实例。于是 fragmentA 就可以调用 mListener 的方法来与 activity 共享事 件。例如,如果 fragmentA 是一个 ListFragment,每次选中列表的一项时,就会调用 fragmentA 的 onListItemClick() 方法,在这个方法中调用 onArticleSelected()来与 activity 共享事件,如下:
public static class FragmentA extends ListFragment{
OnArticleSelectedListenermListener;
...
@Override
public void onListItemClick(ListViewl,Viewv,intposition,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 是列表的被选中的行 ID,另一个 fragment 用这个 ID 来从程序的
ContentProvider 中取得标题的内容。
http://www.yidin.net/?p=9679
更多的移动互联网的发展趋势、拓者设计吧效果图、移动互联网应用相关的资料请到互联网的一点事:www.yidin.net 留言
来源:oschina
链接:https://my.oschina.net/u/1036117/blog/125362