首页 > 移动平台 > 详细

Android Fragment的一些使用细节

时间:2017-03-03 13:07:00      阅读:273      评论:0      收藏:0      [点我收藏+]

作为Android基本组件之一,介绍Android Fragment基本用法的文章已经非常多了,但是Fagment在使用时有很多的细节需要注意。文本主要列举Fragment在使用时需要注意的各种细节。

两类Fragment

Fragment是从Android 3.0(API Level 11) 才开始有的,为了兼容Android 3.0之前的系统,Android在v4包中又包含了一个Fragment的实现。如果APP不需要兼容Android 3.0之前的系统,就使用Android SDK中的Fragment, 如果要兼容,则使用v4包的Fragment。.

这两类Fragment在xml中使用时都用<fragment>标签,不需要区分。但是这两类Fragment在代码中不能通用。Android SDK中的Fragment完整类名为android.app.Fragment,v4包中的Fragment完整类名为android.support.v4.app.Fragment。假如在xml中<fragment>标签下的android:name指定的是继承自android.support.v4.app.Fragment,那么在代码中不能将其赋值给android.app.Fragment对象。反之亦然。

此外,在管理两类Fragment时也需要使用不同的FragmentManager,不能混用。Android SDK中的Fragment使用Android SDK中的FragmentManager,对应的完整类名为android.app.FragmentManager,在Activity中通过getFragmentManager()来获取此对象。v4包中的Fragment使用v4包中的FragmentManager,对应的完整类名为android.support.v4.app.FragmentManager,在Activity中通过getSupportFragmentManager()来获取此对象。

XML中使用Fragment

<fragment>标签

要在XML中使用Fragment需要用<fragment>标签。示例如下。

<fragment
    android:id="@+id/fragment1"
    android:name="cnx.ccpat.testapp.MainFragment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

<fragment>标签首字母小写问题

注意到<fragment>标签中的f是小写的,而在XML布局文件中更常见的首字母大写的各种标签,例如<RelativeLayout>,<TextView>等。之所以这些标签首字母大写,是因为它们都指定了某个具体的View类。而这里的fragment并非指定某个具体的Fragment类(指定具体的Fragment类是通过其android:name属性指定的),它是类似于<include>,<merge>这样的表示某种功能性的占位符,所以<fragment>用小写的f。

参考: http://stackoverflow.com/questions/21948034/why-is-the-fragment-element-in-android-layout-written-with-a-lower-case-f

<fragment>标签的android:name属性

在XML中使用<fragment>必须使用其android:name属性来指定加载的Fragment类名,如果不指定,则会抛出NullPointerException。如图所示。

技术分享

这里需要使用完整的类名,指定的类必须存在,如果指定的类不存在,则会抛出ClassNotFoundException异常。如图所示。

技术分享

指定的类必须是android.app.Fragment或android.support.v4.app.Fragment的子类,但不能是android.app.Fragment或android.support.v4.app.Fragment本身。如果是android.app.Fragment或android.support.v4.app.Fragment本身,则会抛出IllegalStateException,如图所示。

技术分享

这是因为,在FragmentManagerImpl类中调用Fragment的onCreateView()之后会检查Fragment的mView对象是否为null,如果是null就会抛出此异常。而Fragment的mView对象正是Fragment的onCreateView()方法返回的,所以如果Fragment的onCreateView()方法返回了null,就会出现这个异常。android.app.Fragment,android.support.v4.app.Fragment,android.app.DialogFragment,android.support.v4.app.DialogFragment的onCreateView()方法都返回null,所以它们都不能直接在XML中使用。但是它们都是可以在代码中创建,并可以通过FragmentManager添加到布局上,如下调用是合法的。不过这显然没有任何意义。

Fragment fragment = new Fragment();
getSupportFragmentManager().beginTransaction().replace(R.id.fragment1, fragment).commit();

<fragment>标签的android:id属性

<fragment>标签的android:id属性用来标记一个Fragment。在代码中可以通过id来找到对应的Fragment对象,对其进行操作。和一般的View的id不同的是,并不能用加载布局之后的View或Activity的findViewById()方法来获取Fragment对象,而是需要通过FragmentManager的findFragmentById()方法来得到Fragment对象。

例如,在XML中定义了一个Fragment,其id为fragment1,则在代码中可以通过如下方式获取这个Fragment对象。注意,要根据Fragment类型选择使用getFragmentManager()还是getSupportFragmentManager(),如果使用了错误的FragmentManager类型,即使id正确,也无法得到正确的Fragment对象(findFragmentById()会返回null)。

// 如果这个Fragment是继承自android.app.Fragment
getFragmentManager().findFragmentById(R.id.fragment1);
// 如果这个Fragment是继承自android.support.v4.app.Fragment
getSupportFragmentManager().findFragmentById(R.id.fragment1);

一般来说,在XML中定义<fragment>时都需要为其添加android:id属性。

<fragment>标签的android:tag属性

和android:id属性一样,android:tag属性也可以用来标记一个Fragment。在代码中可以通过FragmentManager的findFragmentByTag()方法来得到Fragment对象。

例如,在XML中定义了一个Fragment,其tag为fragment1,则在代码中可以通过如下方式获取这个Fragment对象。

// 如果这个Fragment是继承自android.app.Fragment
getFragmentManager().findFragmentByTag("tag");
// 如果这个Fragment是继承自android.support.v4.app.Fragment
getSupportFragmentManager().findFragmentByTag("tag");

在<fragment>标签中可以同时定义android:id和android:tag属性,则在代码中既可以用findFragmentById()也可以用findFragmentByTag()来获取这个Fragment对象。

<fragment>标签的tools:layout属性

<fragment>标签的tools:layout属性用来标记该Fragment所对应的布局文件,Android Studio会根据此属性在预览时加载对应的布局文件。此属性仅对Android Studio预览时有效,不影响APP的编译和运行。

代码中使用Fragment

生成Fragment对象

要创建一个新的Fragment对象,直接new出来即可。

添加Fragment到布局中

如果要添加Fragment对象到布局中,可以使用FragmentManager的add()方法。FragmentManager有几个重载的add()方法,包含三个参数的add()方法如下。

public FragmentTransaction add(int containerViewId, Fragment fragment, String tag);

这里的三个参数分别说明如下。

containerViewId

containerViewId表示要将Fragment添加到的容器对象,它可以省略,如果省略containerViewId,则表示添加到当前布局中id为0的Layout中。省略containerViewId则必须包含tag。

这里的containerViewId必须对应当前布局中某个已经存在对象,如果当前布局中没有找到containerViewId对应的对象,则会抛出IllegalArgumentException异常。如图所示。

技术分享

containerViewId对应的对象可以是Fragment,也可以是View。

containerViewId对应的对象如果是View的话,该View必须继承自ViewGroup,例如FrameLayout,LinearLayout等。执行add()后,会将新添加的Fragment对应的View作为一个child添加到该ViewGroup中。如果containerViewId对应的View没有继承自ViewGroup,例如它是个TextView,则会抛出ClassCastException异常。如图所示。

技术分享

containerViewId对应的对象如果是Fragment,这时情况会略复杂。首先,containerViewId一定对应XML中某个<fragment>标签的id,如前所述,XML中的Fragment一定是包含View的(其mView成员变量不能是null),而这个View是在Fragment的onCreateView()方法中作为返回值返回得到的,在Fragment的onCreateView()方法通常都是加载另一个XML来得到View对象并返回的,而XML的根标签一定是某个Layout,所以这个View通常也是继承自ViewGroup的。这时执行add()后,会将新添加的Fragment对应的View作为一个child添加到这个ViewGroup中。但是这只是一般情况,事实上,Fragment执行onCreateView()时可以不加载布局文件,直接new一个View来使用也是允许的。如果这个直接new出来的View没有继承自ViewGroup,则执行add()时同样会抛出ClassCastException异常。

举例说明,假设FirstFragment的onCreateView()实现如下,它返回的是一个new出来的TextView。

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return new TextView(getActivity());
}

在XML中有如下布局

<fragment
    android:id="@+id/fragment1"
    android:name="cnx.ccpat.testapp.FirstFragment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

假设我们要添加一个SecondFragment到fragment1中。执行如下代码。

Fragment fragment = new SecondFragment();
getSupportFragmentManager().beginTransaction().add(R.id.fragment1, fragment).commit();

这时就会抛出如下异常。

技术分享

由此可见,无论containerViewId对应的对象如果是Fragment还是View,最终都是将它当做ViewGroup来使用,区别仅在Fragment是将它的mView成员变量作为ViewGroup来使用。

containerViewId除了用来表示添加fragment的容器外,还会被作为新添加的fragment的id。在执行完add()后,就可以通过FragmentManager的findFragmentById(containerViewId)来找到此Fragment。

fragment

fragment是要添加的Fragment对象。

fragment不能是null,否者会抛出NullPointerException,如图所示。

技术分享

已经添加过的Fragment只有在remove之后才可以重新添加,否者会抛出IllegalStateException。如图所示。

技术分享

tag

tag是为该Fragment设置的一个名字,之后可以通过FragmentManager的findFragmentByTag()来查找这个Fragment对象。tag可以省略,如果省略tag,则Fragment没有名字,只能通过id来查找。如果省略tag,则containerViewId不能省略。

查找Fragment

如前所示,对定义在XML中的Fragment,可以通过FragmentManager的findFragmentById()或者findFragmentByTag()来获取,对通过代码添加到布局的Fragment同样也可以用这两个方法来获取。

查找Fragment的关键就是Fragment的id和tag。无论是在XML中定义的Fragment还是通过代码添加的Fragment,Fragment的id和tag都是它被加入到FragmentManager管理之后才被赋予的属性,这点可以从Fragment的方法可以看到,Fragment类只有getId()和getTag()方法,并没有setId()和setTag()方法。一个刚刚创建出来没有被add的Fragment是没有id和tag属性的。对一个已经add的Fragment,如果它被remove了,但remove操作没有被添加到回退栈中,就意味着它不再被FragmentManager管理,也就失去了id和tag。当然,如果remove操作被添加到了回退栈中,被remove的Fragment还是会保留id和tag的。

由于Fragment的id和tag都是它被加入到FragmentManager管理之后才被赋予的属性,所以讨论一个孤立的Fragment的id和tag是没有意义的(孤立的Fragment的id始终为0,tag为null)。这里的查找Fragment也只针对那些FragmentManager可以管理的Fragment,对孤立的Fragment只能通过变量引用的方式使用,不需要再查找。后文讲到Fragment查找时都不包含孤立的Fragment。

id和tag都不具有唯一性,同一个id或tag可能同时被多个Fragment所使用。如果多个Fragment公用一个id或tag,在查找Fragment时,会返回其中最近被add的那个Fragment。

例如,如下代码add了10个Fragment,每个Fragment的id都是一样的(R.id.fragment1),但tag不同(从0到9)。

for (int i = 0; i < 10; i++) {
    Fragment fragment = new SecondFragment();
    getSupportFragmentManager().beginTransaction().add(R.id.fragment1, fragment, String.valueOf(i)).commit();
}

之后,如果执行getSupportFragmentManager().findFragmentById(R.id.fragment1),则会得到最近添加的那个Fragment,也就是tag为9的那个Fragment。

Android Fragment的一些使用细节

原文:http://blog.csdn.net/ccpat/article/details/59736128

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!