深入理解fragment(一)
Fragment是Android3.0引入的新API,可以把Fragment想成Activity中的模块,这个模块有自己的布局,有自己的生命周期,单独处理自己的输入,在Activity运行的时候可以加载或者移除Fragment模块。 可以把Fragment设计成可以在多个Activity中复用的模块,当开发的应用程序同时适用于平板电脑和手机时,可以利用Fragment实现灵活的布局,改善用户体验。
一、Fragment的特征
1.Fragment总是作为Activity界面的组成部分。Fragment可调用getActvity()方法获取它所在的Activity,Activity可调用FragmentManager的findFragmentById()或findFragmentByTag()方法来获取Fragment。其中,fragment的id或tag在其布局文件<fragment../>元素中定义。
2.在Activity运行过程中,可调用FragmentManager的add()、remove()、replace()、方法动态地添加、删除或替换Fragment。
3.一个Activity可以同时组合多个Fragment;反过来,一个Fragment也可能被多个Activity复用;
4.Fragment可以响应自己的输入事件、并拥有自己的声明周期,但它们的生命周期直接被其他所属的Activity的生命周期控制。
二、Fragment的生命周期
因为Fragment必须嵌入在Acitivity中使用,所以Fragment的生命周期和它所在的Activity是密切相关的。如果Activity是暂停状态,其中所有的Fragment都是暂停状态;如果Activity是stopped状态,这个Activity中所有的Fragment都不能被启动;如果Activity被销毁,那么它其中的所有Fragment都会被销毁。但是,当Activity在活动状态,可以独立控制Fragment的状态,比如加上或者移除Fragment。当这样进行fragment
transaction(转换)的时候,可以把fragment放入Activity的back stack中,这样用户就可以进行返回操作。
1.Fragment的几个状态
(1)活动状态:当前Fragment位于前台,用户可见、可以获得焦点;
(2)暂停状态:其他Acitvity位于前台,该Fragment依然可见,只是不能获得焦点;
(3)停止状态:该Fragment不可见,失去焦点
(4)销毁状态:该fragment被完全删除,或者Fragment所在的Activity被结束
2.与Fragment有关的方法
如最常见的就是onCreateView()方法,其作用是返回一个View对象将Fragment显示出来。
三、Fragment所需的库
使用Fragment时,需要继承Fragment或者Fragment的子类(DialogFragment, ListFragment, PreferenceFragment, WebViewFragment),所以Fragment的代码看起来和Activity的类似。由于Fragment是Android
3.0及以上系统特有的API,如果我们需要开发低版本的应用软件,则需要引入Fragment所支持的库。
Support Library是一个提供了API库函数的JAR文件,这样就可以在旧版本的Android上使用一些新版本的APIs.比如android-support-v4.jar.它的完整路径是:
<sdk>/extras/android/support/v4/android-support-v4.jar. 它就提供了Fragment的APIs,使得在Android
1.6 (API level 4)以上的系统都可以使用Fragment。使用该支持库需要分两步:
1.添加支持库到工程项目中
将上述的包拷入libs项目下的libs文件夹,然后在项目的Properties中添加:右键单击项目,选Properties,左边选Java
Build Path,然后Add External JARs…,添加android-support-v4.jar.
2.导入Fragment、FragmentManager到工程中
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
注意,当创建包含Fragment的Activity时,如果用的是Support Library,那么继承的就应该是FragmentActivity而不是Activity.
四、Fragment核心剖析
1.创建Fragement,并设置该Fragment的显示界面
与创建Activity类似,开发者实现的Fragment必须继承Fragment基类,Android提供了4种Fragment:(1)DialogFragment-对话框界面的Fragment;(2)ListFragment-实现列表界面的Fragment;(3)PreferenceFragment-选项设置界面的Fragment;(4)WebViewFragment-WebView界面的Fragment。创建Fragment通常需要实现三个回调方法:
(1)onCreate()
系统在创建Fragment的时候调用这个方法,这里应该初始化相关的组件,一些即便是被暂停或者被停止时依然需要保留的东西。
(2)onCreateView()
当第一次绘制Fragment的UI时系统调用这个方法,必须返回一个View。组件View对应的布局文件,即为该Fragment将会显示的界面。如果Fragment不提供UI也可以返回null。
注意,如果继承自ListFragment,onCreateView()默认的实现会返回一个ListView,所以不用自己实现。
(3)onPause()
当用户离开Fragment时第一个调用这个方法,需要提交一些变化,因为用户很可能不再返回来。
示例1:实现一个Fragment的布局界面。通过onCreateView()方法,将example_fragment布局资源文件设置为该Fragment的显示界面。
源码:
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, //指明了当前的Fragment对应的资源文件
container, //父容器控件
false); //*表明是否链接该布局和其父容器。这里设置为false,是因为系统已经插入了
这个布局到父控件,设置为true将会产生多余的一个View Group*/
}
}
注释:onCreateView方法中container参数代表该Fragment在Acitivity的父控件;savedInstanceState提供了上一个实例的数据。
2.把一个Fragment加入到Activity中
Fragment不能单独存在,必须依附在Activity上。当Fragment被加入到指定的Activity中时,它会处在对应的View
Group中。通常,Fragment加载到一个Activity有两种方式:一种是在layout中使用标签<fragment>声明;另一种方法是在代码中把Fragment加入到一个Activity的指定ViewGroup中。
(1)通过被加载Activity的布局文件将Fragment加载到指定Acitivity
我们只需在指定Activity的布局文件中,添加一个<fragment.../>元素将Fragment作为一个子标签加入即可。
示例2:实现加载一个列表Fragment到指定的Activity中显示
源码:
<?xml version="1.0" encoding="utf-8"?>
<!-- 定义一个水平排列的LinearLayout,并指定使用中等分隔条 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:divider="?android:attr/dividerHorizontal"
android:showDividers="middle">
<!-- 使用资源文件方式:添加一个fragment到Activity中 -->
<fragment
android:name="com.example.android_fragment_1.BookListFragment"
android:id="@+id/book_list"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1"/>
</LinearLayout>
注释:android:name属性为该需要加载的Fragment完整类名(包.类名);android:id属性为该Fragment的唯一标识符ID,我们可以通过ID实现对该Fragment进行添加、删除等操作。
(2)利用FragmentManager和FragmentTransaction将一个Fragment加载到指定Activity的ViewGroup中
当Activity处于Running状态下的时候,可以使用这种方法在Activity的布局中动态地加入Fragment,只需要指定加入这个Fragment的父View
Group即可。
FragmentManager主要完成获取指定Fragment,将Fragment从后台弹出栈、为后台Fragment注册一个监听器。
FragmentTransaction则完成Fragment的添加、删除、替换等事务,每个FragmentTransaction可以包含多个对Fragment修改,比如包含调用了多了add()、remove()和replace()操作,最后还调用commit()方法提交事务即可。
首先、通过FragmentManager对象获得一个FragmentTransaction对象,借助FragmentTransaction对象实现Acitivity对Fragment执行添加、删除、替换等操作。
FragmentManager fragmentManager=getFragmentManager(); //如用的是支持包,则为getSupportFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();//打开事务
其次,将实现的名为BookListFragment的Fragment添加到View
Group中
BookListFragment booklistfragment = new BookListFragment(); //创建一个图书列表Fragment
fragmentTransaction.add(R.id.fragment_container, booklistfragment );
//添加图书列表Fragment到标识为fragment_container容器
fragmentTransaction.commit(); //提交事务改变生效-使View容器显示图书列表Fragment
注释1:如果想使应用程序允许用户按BACK返回键回到替换Fragment之前的状态(即上一个Fragment),我们可以将事务添加到back栈。
fragmentTransaction.addtoBackStack(null);
注释2:除了上述添加一个Fragment到Activity的View父容器中,还可以替换或者删除容器中已有的Fragment.调用commit()方法使得FragmentTransaction实例的改变生效。
fragmentTransaction.replace(R.id.fragment_container, booklistfragment);
//替换容器类已有的Fragment
注释3:R.id.fragment_container为当前Activity布局中ID为fragment_container的容器,在Activity界面布局文件中定义。
res/layout/main.xml
3.Fragment与Activity通信
通过第二步将一个Fragment加载到指定的Activity之后,该Fragment必须与Activity交互信息,这就需要Fragment能获取它所在的Activity,Activity也能获取它所包含的任意的Fragment。
1)获取目标对象
(1)Fragment获取它所在的Activity:
调用Fragment的getActivity()方法即可返回它所在的Activity。
(2)Activity获取它所包含的Fragment:
调用Activity关联的FragmentManager的findFragmentById(int
id)或findFragmentByTag(String tag)方法即可获取指定的Fragment。
ExampleFragment fragment =(ExampleFragment)getFragmentManager().findFragmentById(R.id.example_fragment)
注释:我们可以在Activity界面元件中使用<fragment.../>添加Fragment时,可以为<fragment../>元素添加android:name或android:tag属性来标识Fragment,接下来Activity将可以通过findFragmentById(int
id)等方法来获取Fragment。
2)Frament与Activity之间的数据传递
(1)Activity向Fragment传递数据:
在Activity中创建Bundle数据包,并调用Fragment的setArguments(Bundle
bundle)方法即可将Bundle数据包传给Fragment;
(2)Fragment向Activity传递数据或Activity需在Fragment运行中进行实时通信:
在Fragment中定义一个内部回调接口,再让包含该Fragment的Activity实现该回调接口,这样Fragment即可调用该回调方法将数据传给Activity。
3)activity响应fragment的事件完全解析
有时,你可能需要fragment与activity共享事件。一个好办法是在fragment中定义一个回调接口,然后在activity中实现这个回调接口。
例如,还是那个新闻程序的例子,它有一个activity,activity中含有两个fragment。fragmentA显示新闻标题,fragmentB显示标题对应的内容。fragmentA必须在用户选择了某个标题时告诉activity,然后activity再告诉fragmentB,fragmentB就显示出对应的内容(为什么这么麻烦?直接fragmentA告诉fragmentB不就行了?也可以啊,但是你的fragment就减少了可重用的能力。现在我只需把我的事件告诉宿主,由宿主决定如何处置,这样是不是重用性更好呢?)。如下例,
(1)在fragmentA(BookListFragment)中定义OnBookSelectedListener回调接口:
public class BookListFragment extends ListFragment
{
private OnBookSelectedListener whichbook; //接口对象
public interface OnBookSelectedListener
{
public void onBookSelected(Integer
id); //参数为Map集合的键
}
.....
}
(2)
然后在activity实现接口OnBookSelectedListener,在方法onBookSelected()中通知fragmentB(BookDetailFragment
),即Activity从FragmentA获取传入的ID,用来启动FragmentB。
如下所示:
public class SelectBookActivity extends Activity implements BookListFragment.OnBookSelectedListener
{
......
public void onItemSelected(Integer id)
{
//a.创建Bundle,准备向Fragment传入参数
Bundle bundle=new Bundle();
bundle.putInt(BookDetailFragment.ITEM_ID,
id); //装入值id到"item_id"键
//b.创建BookDetailFragment对象,并项Fragment传入参数
BookDetailFragment fragment=new BookDetailFragment();
fragment.setArguments( bundle);
//c.使用fragment替换book_detail_container容器当前显示的Fragment
getFragmentManager().beginTransaction()
.replace(R.id.book_detail_container, fragment)
.commit();
/*注释:这一句等价于....
* FragmentManager Manager=getFragmentManager();
* FragmentTransaction Transaction=Manager.beginTransaction();
* Transaction.replace(R.id.book_detail_container, Manager);
* Transaction.commit();
* */
}
(3)实现fragmentA(BookListFragment)的onAttach()方法,将fragmentA添加并显示到Activity中
当fragmentA添加到activity中时,会调用fragmentA的方法onAttach(),这个方法中适合检查activity是否实现了OnBookSelectedListener接口,检查方法就是对传入的activity的实例进行类型转换,如果activity没有实现那个接口,fragment抛出ClassCastException异常。如果成功了,mListener成员变量保存OnArticleSelectedListener的实例。于是fragmentA就可以调用mListener的方法来与activity共享事件。
public void onAttach(Activity activity)
{
super.onAttach(activity);
//a.如果Activity中没有实现Callbacks接口,抛出异常
if(!(activity instanceof Callbacks))
{
throw new IllegalStateException("异常:BookListFragment所在的Activity必须实现Callback接口!");
}
//把该Activity当成whichbook对象(缺少这一句话,导致出现NullPointerException错误)
whichbook=( BookListFragment)activity;
}
(4)在fragmentA中实现onListItemClick()响应activity发出的事件(Fragment向Activity传递ID)
如果fragmentA是一个ListFragment,每次选中列表的一项时,就会调用fragmentA的onListItemClick()方法,在这个方法中调用onBookSelected()来与activity共享事件。onBookSelected()传入的参数id是列表的被选中的行ID,另一个fragment(B)( BookDetailFragment )用这个ID来从程序的ContentProvider中取得标题的内容。
@当用户单击某列表项时激发该回调方法
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
OnBookSelectedListener.onBookSelected(BookContent.ITEMS.get(position).id);//激发 OnBookSelectedListener接口的onBookSelected方法
}