购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

5.6 Fragment

5.6.1 Fragment简介

Fragment,碎片,是Android 3.0(API level 11)之后新增加的特性。Fragment必须被嵌入到Activity中使用,并且它的生命周期直接受到Activity的生命周期的影响,当Activity被暂停时,在它里面的Fragment也会被暂停,当Activity被销毁时,它里面所有的Fragment都会被销毁。在Activity中的Fragment可以理解为一种行为或者用户UI界面的一部分,可以在一个Activity中包含一个或者多个Fragment,结合起来创建一个UI布局,并且可以在多个Activity中重复使用同一个Fragment。在Activity的运行周期内,Fragment拥有自己的生命周期,接收自己的监听事件,并且可以在Activity运行时,通过Fragment事务动态添加或者移除Fragment,并把Fragment事务加入到Activity的任务栈中,这样即便是点击了回退键(向后导航),返回的也是上一个Fragment操作,而非上一个Activity。

接下来将详细介绍Fragment的使用。

5.6.2 Fragment的设计理念

Fragment,主要是为了让大屏幕设备上能支持更具有动态性和灵活性的UI设计,如平板电脑。因为现在Android设备的屏幕越来越大,使用Fragment可以更灵活的管理视图层次的变化。Fragment像Activity一样,可以创建Fragment来加载一个View进行布局,但是Fragment必须嵌入在Activity中,不能单独存在,而且一个Activity可以嵌入多个Fragment,同时一个Fragment可以被多个Activity重复使用。

上图以很清晰的说明Activity和Fragment的关系。在平板电脑中,因为屏幕大,显示的内容全,如果通过Activity跳转的方式去加载新的Activity显示详细内容,将浪费屏幕资源。而如左上图所示,可以结合Fragment布局,使一个Activity左右分别包含一个Fragment,这样可以通过对左边Fragment的操作来影响右边Fragment的显示内容,例如:新闻阅读,系统设置等。如果一个应用的是采用Activity+Fragment结合的方式布局,那么可以很方便的在平板与手机之间相互移植,大部分代码是可以重复利用的,并且Fragment无需在AndroidManifest.xml清单文件中注册。

5.6.3 创建一个Fragment

使用Fragment必须继承这个类或其子类,Fragment或其子类的代码包含了一些类似于Activity的生命周期方法,如:onCreate()、onStart()、onPaused()、onStop()等。实际上如果需要把一个现有的Activity转换成一个Fragment,只需要简单的移植Activity的一些方法即可。

通常情况下,并不需要对Fragment包含的生命周期方法全部重写,只需要重写其中的一部分。一般而言,会重写如下几个Fragment方法:

· onCreate():当创建Fragment的时候会调用这个方法,一般在这个方法中做一些Fragment初始化的工作。

· onCreateView():当Fragment首次被绘制在UI界面上时,系统会调用这个方法,返回一个View对象,这个View是Fragment布局的根View,如果Fragment不需要显示,那么可以返回一个null。

· onPaused():在用户暂停Fragment的时候被调用,应该在其中做Fragment被销毁前的准备工作,例如回收资源、保存临时数据等。

在上述三个推荐实现的Fragment生命周期方法中,着重讲解onCreateView()方法,它用来加载Fragment的显示View。onCreateView()的完整签名如下:

public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState)

其中参数的意义如下:

· inflater:当前布局的内容填充者,可以用inflater.inflate()方法去填充一个布局资源文件。

· container:为包裹当前Fragment的容器,一般是一个布局控件。

· savedInstanceState:如果为非空,表示之前被非正常销毁时保存的状态。

onCreateView()方法返回一个View对象,用于表示Fragment显示的视图,在其中可以使用inflater.inflate()方法动态膨胀一个XML布局资源,得到Fragment显示的View。inflate()方法的完整签名如下:

public View inflate(int resource,ViewGroup root,boolean attachToRoot)

其中参数的意义如下:

· resource:动态膨胀的布局资源ID。

· root:膨胀出的View的上层布局对象,一般传递onCreateView的container参数即可。

· attachToRoot:指定展开的布局是否依附到root这个ViewGroup中,一般传递false即可。

使用Fragment需要通过<fragment…/>元素来在XML布局资源文件中添加Fragment,它的属性与大多数UI组件一样,但是有两个属性需要特别注意:

· android:name:这个Fragment的实现类。

· android:layout_weight:当前Fragment在Activity中的权重,数值越大,在Activity中占的空间越大。

示例:演示如何在Activity中使用Fragment进行布局,在此示例中,为一个Activity中加入两个Fragment。

首先创建Activity的布局资源文件。

代码清单:\codes\05\05\FragmentDemo\res\layout\activity_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <fragment
        android:id="@+id/fragment1"
        android:name="com.example.fragmentSimple.Fragment1"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="2" />

    <fragment
        android:id="@+id/fragment2"
        android:name="com.example.fragmentSimple.Fragment2"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1" />

</LinearLayout>

定义两个Fragment,分别加载一个简单的XML布局资源。

代码清单:\codes\05\05\FragmentDemo\src\com\bookdemo\fragmentSimple\Fragment1.java

public class Fragment1 extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // 填充一个布局View到ViewGrope中
        return inflater.inflate(R.layout.fragment1, container, false);        
    }
}

代码清单:\Fragment2.java

public class Fragment2 extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment2, container, false);
    }
}

在模拟器上运行效果如下图:

5.6.4 Fragment的生命周期

Fragment只能依托于Activity,但是在Activity的运行时,Fragment还具有自己的生命周期,除了前面介绍过的一些Fragment生命周期方法,Fragment还提供了不同的与生命周期对应的方法,如下图所示:

从上图中可以看出Fragment的生命周期和Activity基本一样,但有两个生命周期方法需要注意,即onAttach()附加和onDetach()剥离。从这两个方法的位置可以看出,Fragment在创建的时候,是先附加到Activity中,然后才开始从onCreateView()中加载View的。在生命周期结束的时候,也是先销毁onDestroy()然后才回调onDetach()从Activity中剥离这个Fragment。

5.6.5 管理Fragment

要管理Fragment,必须保证这个Fragment能够被找到,所以必须在创建Fragment的时候,为它指定一个唯一的标识,方便Activity在运行时通过此标识找到待操作的Fragment。有三种方式可以为Fragment提供唯一的标识:

· 使用android:id属性,为Fragment指定一个唯一的ID。

· 使用android:tag属性,为Fragment指定一个唯一的标识。

· 如果以上两种属性均没有设置,那么系统会使用包裹容器中的ID

Fragment具有唯一标识之后,还需要使用Activity中的FragmentManager类来管理其中的Fragment。为了获得FragmentManager对象,可以在Activity中调用getFragmentManager()方法。使用FragmentManager可以做的的事情包括:

· 使用findFragmentByID()或者findFragmentByTag()方法从Activity中获得唯一的Fragment对象

· 使用popBackStack()方法模拟一个向后导航,使当前回退栈中的Fragment弹栈。

· 使用addOnBackStackChangeListener()方法注册一个回退栈改变的监听器,监听在回退栈中加入Fragment的事件

Fragment很大的一个特点就是可以在Activity中动态的添加、移除和执行。在Activity中对Fragment的每一组改变被称为一个Fragment事务,以FragmentTransaction对象表示,这个对象可以通过FragmentManager.beginTransaction()方法获得,它将开启一个Fragment事务,用于操作ViewGroup中的Fragment。

FragmentTransaction的常用方法有:

· add():增加一个Fragment,具有多个重载。

· replace():替换一个Fragment,具有多个重载。

· remove():移除掉一个指定的Fragment。

· addToBackStack():将此次改变Fragment的事务添加到Activity的任务栈中。

· commit():提交事务的改变。

其中add、replace、remove都是很常见的方法。但是addToBackStack()方法在正常情况下,应用中的Activity是有一个任务栈去管理它的。默认情况下,在不同的Activity中跳转时,点击回退总是能回到上一个Activity中。而Fragment是嵌套在Activity,所以默认无法向Activity的任务栈中添加,当点击回退的时候只会回到上一个Activity,不会理会Fragment的操作(add、replace、remove),而使用addToBackStack()可以将当前的事务添加到另一个栈中,这个栈由Fragment的Activity管理,这个栈中的每一条都是一个Fragment的一次事务,有了这个栈去管理Fragment,就可以通过回退按键,反向回滚Fragment的事务。这一点很重要,因为Fragment无需在清单文件中配置,所以现在有些应用会使用Fragment来布局跳转。在每次Fragment事务的结束,还必须调用commit()方法,用于提交事务,否则不予执行。

示例:演示如何使用Fragment事务动态操作Fragment。在示例中,会实现一个类似于分栏的效果,在左边点击不同的项会动态改变右边的内容。

首先定义一个Activity的布局资源文件。

代码清单:\codes\05\05\FragmentDemo\res\layout\activity_fragmenttab.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tabfgt1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="fragment1" />

        <TextView
            android:id="@+id/tabfgt2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="fragment2" />

        <TextView
            android:id="@+id/tabfgt3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="fragment3" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    </LinearLayout>

</LinearLayout>

再声明一个Activity,使用Fragment事务动态操作Fragment。

代码清单:\codes\05\05\FragmentDemo\src\com\bookdemo\fragmentTab\FragmentTabActivity.java

public class FragmentTabActivity extends Activity {
    private TextView tabfgt1, tabfgt2, tabfgt3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragmenttab);

        tabfgt1 = (TextView) findViewById(R.id.tabfgt1);
        tabfgt2 = (TextView) findViewById(R.id.tabfgt2);
        tabfgt3 = (TextView) findViewById(R.id.tabfgt3);

        tabfgt1.setOnClickListener(click);
        tabfgt2.setOnClickListener(click);
        tabfgt3.setOnClickListener(click);

    }

    private View.OnClickListener click = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            tabfgt1.setBackgroundColor(Color.GRAY);
            tabfgt2.setBackgroundColor(Color.GRAY);
            tabfgt3.setBackgroundColor(Color.GRAY);
            // 获取FragmentManager对象
            FragmentManager fm = getFragmentManager();
            // 开启事务
            FragmentTransaction ft = fm.beginTransaction();
            switch (v.getId()) {
            case R.id.tabfgt1:
                tabfgt1.setBackgroundColor(Color.GREEN);
                // 替换R.id.content中的Fragment
                ft.replace(R.id.content, new Fragment1());
                break;
            case R.id.tabfgt2:
                tabfgt2.setBackgroundColor(Color.YELLOW);
                ft.replace(R.id.content, new Fragment2());
                break;
            case R.id.tabfgt3:
                tabfgt3.setBackgroundColor(Color.RED);
                ft.replace(R.id.content, new Fragment3());
                break;
            default:
                break;
            }
            // 提交事务
            ft.commit();
        }
    };
}

在模拟器上运行效果如下:

5.6.6 在Fragment中交互

如何操作两个不同Fragment中的控件?在Activity中,操作一个控件需要通过findViewById(int)方法通过控件的ID去找到控件,而使用Fragment其实到最后Fragment.onCreateActivity()的时候是把膨胀的View加载到Activity中,可以直接在Activity中通过findViewById()方法找到控件,进而操作它,这一点和直接操作Activity的方式一致。但是如果需要在一个Fragment中操作另外一个Fragment的控件,就需要用到Fragment.getActivity()来获取到当前Fragment承载的Activity对象,拿到这个Activity对象,获取到其中的控件就不成问题了。

示例:演示如何在不同的Fragment中进行交互。在Activity中加载三个Fragment,在其中一个Fragment中的Button点击的时候,修改其它Fragment的值。

首先声明一个XML布局资源文件,其中加载了三个Fragment,且有两个Fragment是一样的。

代码清单:\codes\05\05\FragmentDemo\res\layout\activity_fragmentturn.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <fragment
        android:id="@+id/fragment1"
        android:name="com.example.fragmentSimple.Fragment1"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    <!-- 加载了两个Fragment1 -->
         <fragment
        android:id="@+id/fragment2"
        android:name="com.example.fragmentSimple.Fragment1"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    <fragment
        android:id="@+id/fragment3"
        android:name="com.example.fragmentTurn.Fragment3"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1" />

</LinearLayout>

声明的第三个Fragment带Button组件,在其中添加点击事件监听器,修改其它Fragment的数据。

代码清单:\codes\05\05\FragmentDemo\src\com\bookdemo\fragmentTurn\Fragment3.java

public class Fragment3 extends Fragment {
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment3, container, false);        
    }
    @Override
    public void onStart() {
        super.onStart();
        // 方法2: 在Fragment中获取操作其它Fragment的控件
        Button btnGetText2=(Button)getActivity().findViewById(R.id.btnGetText2);
        btnGetText2.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // 使用FragmentManager通过ID找到需要操作的fragment对象
                Fragment fragment=getFragmentManager().findFragmentById(R.id.fragment2);
                // 获得Fragment对象显示的根View
                View view=fragment.getView();
                // 找到控件
                TextView tv=(TextView) view.findViewById(R.id.tvFragment1);
                tv.setText("Fragment中动态改变");        
            }
        });
    }
}

声明一个Activity来承载XML布局资源,并且在其中为第三个Fragment的一个Button组件添加点击事件修改其它Fragment的数据。

代码清单:\codes\05\05\FragmentDemo\src\com\bookdemo\fragmentTurn\FragmentTurnActivity.javas

public class FragmentTurnActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragmentturn);
        // 方法1:在Activity中操作旗下Fragment中的控件
        Button btn=(Button)findViewById(R.id.btnGetText);
        btn.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                
                TextView tv=(TextView)findViewById(R.id.tvFragment1);
                tv.setText("Activity中动态修改");
            }
        });
    }
}

在模拟器上运行,分别点击Fragment3下的两个按钮,效果如下图:

上面的例子有一个问题,无论是在Activity中使用findViewById()还是在Fragment中使用getActivity().findViewById(),虽然可以获取到控件,但是有个例外的情况。就是在Activity中,同时使用了两个相同的Fragment,这个时候仅仅使用上面介绍的方法,只能通过id获取到第一个Fragment中的控件。因为,在布局文件中定义的控件,就算ID重复了,Android SDK自动维护的R.java类中,也只会声明一次,想在Activity中通过控件ID区分同一个Fragment类的两个实例中的控件,是无法做到的。

那么就需要换一个思路,可以在Fragment使用getFragmentManager().findFragmentById(int)通过Fragment的Id找到待操作的Fragment对象,然后使用getView()方法获得其中的View对象,使用View.findViewById(int)找到Fragment的对应Id的控件,进而操作它,如示例所示。

5.6.7 Fragement向下兼容

为了兼容低版本的Android设备,Google推出了android.support.v4.app.Fragment开发包。如果考虑向下兼容的问题,推荐直接引用android.support.v4.app.Fragment包进行开发。 KPa9PSETvr42F4RqBWMylgPL9H8cBydRM+vON1WYsYFirO+qwzeKykbBFch+UWGr

点击中间区域
呼出菜单
上一章
目录
下一章
×