首页 > 代码库 > Android UI编程(7)——Fragment

Android UI编程(7)——Fragment

Fragment是Activity的界面中的一部分或一种行为。你可以把多个Fragment们组合到一个Activity中来创建一个多面界面并且可以在多个Activity中重用一个Fragment。也可以把Fragment认为模块化的一段Activity,它具有自己的生命周期,接收它自己的事件,并可以在Activity运行时被添加或删除。

Fragment不能独立存在,它必须嵌入到activity中,而且Fragment的生命周期直接受所在的Activity的影响。例如:当Activity暂停时,它拥有的所有的Fragment们都暂停了,当Activity销毁时,它拥有的所有Fragment们都被销毁。然而,当Activity运行时(在onResum()之后,onPause()之前),你可以单独地操作每个Fragment,比如添加或删除它们。当在执行上述针对Fragment的事务时,你可以把事务添加到一个栈中,这个栈被Activity管理,栈中的每一条都是一个Fragment的一次事务。有了这个栈,就可以反向执行Fragment的事务了,这样就可以在Fragment级支持"返回"键(向后导航)。

当向Activity中添加一个Fragment时,它须置于ViewGroup控制中,并且需定义Fragment自己的界面。你可以在layout xml文件中声明Fragment,元素为:<fragment>;也可以在代码中创建Fragment,然后把它加入到ViewGroup控件中。然而,Fragment不一定非要放在Activity的界面中,它可以隐藏在后台为Activity工作。

Fragment要点

1、Fragment作为Activity界面的一部分组成出现

2、可以在一个Activity中同时出现多个Fragment,并且,一个Fragment也可在多个Activity中使用

3、在Activity运行过程中,可以添加、移除或者替换Fragment(add()、remove()、replace())

设计哲学

Android从3.0开始引入Fragment,主要是为了支持更动态更灵活的界面设计,比如在平板上的应用。平台机上拥有比手机更大的屏幕空间来组合和交互界面组件们。Fragment使在做这样的设计时,不需要应付View树中复杂的变化。通过把Activity的layout分成Fragment,就可以在Activity运行时改变它的样子,并且可以在Activity的后退栈中保存这个改变。

例如:写一个读新闻的程序,可以用一个fragment显示标题列表,另一个Fragment显示选中标题的内容,这两个Fragment都是在一个Activity上,并排显示。那么这两个Fragment都有自己的生命周期并响应自己感兴趣的事件。于是,不需要像手机上那样一个Activity显示标题列表,另一个Activity显示新闻内容;现在可以把两者放在一个Activity上同时显示。如下图:

技术分享

Fragment必须被写成可重用的模块。因为Fragment有自己的layout,自己进行事件相应,拥有自己的生命周期和行为,所有可以在多个Activity中包含一个Fragment的不同实例。这个对于你的界面在不同的屏幕尺寸下都能用户完美的体验尤其重要。比如可以在程序运行大屏幕中时启动包含很多Fragment的Activity,而在运行于小屏幕时启动一个包含少量Fragment的Activity。

举个例子:还是刚才那个读新闻的程序,当检测到程序运行于大屏幕时,启动activity A,将标题列表和新闻内容这个两个Fragment都放在Activity A中;而当检测到程序运行于小屏幕时,还是启动Activity A,但此时A中只有标题列表Fragment,当选择一个标题时,Activity A启动Activity B,B中含有新闻内容Fragment。

创建Fragment

要创建一个Fragment,必须从Fragment或Fragment的派生类派出一个新类。Fragment的代码写起来有些像Activity。它具有跟Activity一样的回调方法,比如onCreate()、onStart()、onPause()和onStop()。实际上,如果想把老的程序改为使用Fragment,基本上只需要把Activity的回调方法的代码移到Fragment中对应的回调方法即可。

通常需要实现以下生命周期函数

onCreate()——当创建Fragment时系统调用此方法。在其中你必须初始化Fragment的基础组件们,可参考Activity的说明

onCreateView()——系统在Fragment要画自己的界面时调用(在正在显示之前)此方法。这个方法必须返回Fragment的layout的根控件。如果这个fragment不提供界面,可以返回null。即,Fragment第一次绘制它的用户界面的时候,系统会调用此方法。为了绘制Fragment的UI,此方法必须返回一个View,这个View是Fragment布局的根View。如果Fragment不提供UI,可以返回null。

onPause()——用户将要离开Fragment时,系统调用这个方法作为第一个指示(然而它不总是意味着Fragment将被销毁)。当前面用户绘画结束之前,通常应当在这里提交任何应该持久的变化(因为用户有可能不会返回)。

Fragment的生命周期

技术分享

除了继承基类Fragment,还有一些子类可以继承

DialogFragment

显示一个浮动的对话框,用这个类来创建一个对话框是使用在Activity类的对话框工具方法之外的一个好选择,因为可以将一个Fragment对话框合并到Activity管理的Fragment back stack(回退栈)中,允许用户返回一个之前曾被摒弃的Fragment。

ListFragment

显示一个由Adapter(例如SimpleCursorAdapter)管理的项目的列表,类似于ListActivity。它提供一些方法来管理一个List View,例如onListItemClick()回调来处理点击事件。

PreferenceFragment

显示一个Preference对象的层次结构的列表,类似于PreferenceActivity。这个在为应用创建一个"设置"Activity时有用处

例子一

AndroidManifest.xml——没有做任何修改,创建工程默认

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wxl.fragment"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.wxl.fragment.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
fragment1.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:background="#ffcccc"
    android:orientation="vertical" >
    
    <TextView 
        android:id="@+id/fragment1_textView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="第一个Fragment界面"/>

</LinearLayout>
fragment2.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:background="#fffccc"
    android:orientation="vertical" >
    
	<TextView 
        android:id="@+id/fragment2_textView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="第二个Fragment界面"/>    

</LinearLayout>

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity" >

    <fragment 
        android:id="@+id/fragment1"
        android:layout_width="0dip"
        android:layout_height="match_parent"
        android:name="com.wxl.fragment.Fragment1"
        android:layout_weight="1"/>
    <fragment 
        android:id="@+id/fragment2"
        android:layout_width="0dip"
        android:layout_height="match_parent"
        android:name="com.wxl.fragment.Fragment2"
        android:layout_weight="1"/>

</LinearLayout>

Fragment1.java

package com.wxl.fragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment1 extends Fragment {
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		return inflater.inflate(R.layout.fragment1, container,false);
	}
}
Fragment2.java

package com.wxl.fragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment2 extends Fragment {
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		return inflater.inflate(R.layout.fragment2, container,false);
	}
}
MainActivity.java

package com.wxl.fragment;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
注意

在Activity中静态使用Fragment的时候,要注意两个地方:

Fragment引用的包是:import android.support.v4.app.Fragment;而不是import android.app.Fragment;

然后Activity必须是继承FragmentActivity,引用包是:import android.support.v4.app.FragmentActivity;

截图

技术分享

Logcat打印信息:(FragmentActivity)

技术分享

添加用户界面

Fragment通常用来作为一个Activity的用户界面的一部分,并将它的layout提供给Activity。为了给一个Fragment提供一个layout,必须实现onCreateView()回调方法,当到了Fragment绘制它自己的layout的时候,Android系统调用调用它。此方法的实现代码必须返回一个Fragment的layout的根View。

public class Fragment1 extends Fragment {
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		return inflater.inflate(R.layout.fragment1, container,false);
	}
}
传入onCreateView()的container参数是Fragment layout将被插入的父ViewGroup(来自Activity的layout)。savedInstanceState参数是一个Bundle,如果Fragment是被恢复的,它提供关于Fragment的之前的实例数据。

inflater.inflate(R.layout.fragment1, container,false);

  • 参数一:想要加载的layout的resource ID
  • 参数二:加载的layout的父ViewGroup。传入container是很重要的,目的是为了让系统接受所要加载的layout的根View的layout参数,由它将挂靠的父View指定
  • 参数三:布尔值指示在加载期间,展开的layout是否应附着到ViewGroup(第二个参数)。(在这个例子中,指定了false,因为系统已经把展开的layout插入到container,传入true会在最后的layout中创建一个多余的ViewGroup)。
将Fragment添加到Activity
通常地,Fragment为宿主Activity提供UI的一部分,被作为Activity的整个的一部分被嵌入。有两种方法可以添加一个Fragment到Activity layout中。

方法一:在Activity的layout文件中声明fragment

在这种情况下,可以像为View一样,为fragment指定layout属性:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity" >

    <fragment 
        android:id="@+id/fragment1"
        android:layout_width="0dip"
        android:layout_height="match_parent"
        android:name="com.wxl.fragment.Fragment1"
        android:layout_weight="1"/>
    <fragment 
        android:id="@+id/fragment2"
        android:layout_width="0dip"
        android:layout_height="match_parent"
        android:name="com.wxl.fragment.Fragment2"
        android:layout_weight="1"/>

</LinearLayout>
<fragment>中的android:name属性指定了在layout中实例化的Fragment类。

当系统创建这个Activity layout时,它实例化每一个在layout中指定的fragment,并调用每一个上的onCreateView()方法,来获取每一个fragment的layout。系统将从返回的View直接插入到<fragment>元素所在的地方。

注意:每一个fragment都需要一个唯一的标识,如果Activity重启,系统可以用来恢复fragment(并且也可以用来捕获fragment来处理事务,例如移除它)

有3种方法来为一个fragment提供一个标识:

  • 为android:id属性提供一个唯一ID
  • 为android:tag属性提供一个唯一字符串
  • 如果以上2个都没有提供,系统使用容器view的ID
方法二:编写代码将fragment添加到一个已存在的ViewGroup
当Activity运行的任何时候,都可以将fragment添加到Activity layout中。只需要简单的指定一个需要放置fragment的ViewGroup为了在Activity中操作fragment事务(例如添加、移除、或者替换一个fragment),必须使用来自FragmentTransaction的API
可以按如下方法,从Activity取得一个FragmentTransaction的实例
FragmentManager fragmentManager = getFragmentManager(); 
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
然后可以使用add()方法添加一个fragment,指定要添加的fragment和要插入的View
Fragment1 fragment = new Fragment1();  
fragmentTransaction.add(R.id.main,fragment);  
fragmentTransaction.commit();
add()第一个参数是fragment要放入的ViewGroup,由resource ID指定。第二个参数是需要添加的fragment。一旦用FragmentTransaction做了改变,为了使改变生效,必须调用commit()。其中Fragment1是自己继续Fragment基类或派生类的新类。

FragmentManager

FragmentManager能够实现管理Activity中的fragment,通过调用Activity的getSupportFragmentManager()取得它的实例。

FragmentManager可以做的一些事情

1、使用findFragmentById()(用于在Activity layout中提供一个UI的fragment)或findFragmentByTag()(适用于有或没有UI的fragment)获取Activity中存在的fragment

2、将fragment从后台堆栈中弹出,使用popBackStack()(模拟用户按下Back命令)

3、使用addOnBackStackChangeListenner()注册一个监听后台堆栈变化的Listenner

FragmentTransaction

FragmentTransaction对fragment进行添加、移除、替换、以及执行其他动作。从FragmentManager获得一个FragmentTransaction的实例:

FragmentManager fragmentManager = getFragmentManager(); 
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每一个事务都是同时要执行的一套变化,可以在一个给定的事务中设置你想执行的所有变化,使用诸如add()、remove()、replace(),然后给Activity应用事务,必须调用commit()。

例子二——根据上述例子一,对上述的activity_main.xml、Fragment1.java、MainActivity.java做了部分修改,修改如下
Fragment1.java
将原来导包import android.support.v4.app.Fragment;该为import android.app.Fragment;
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity" >
    
    <LinearLayout 
        android:id="@+id/main_linearLayout1"
        android:layout_width="0dip"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical">
        
    </LinearLayout>
    
    <LinearLayout 
        android:id="@+id/main_linearLayout2"
        android:layout_width="0dip"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical">
        
        <fragment 
        android:id="@+id/fragment2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.wxl.fragment.Fragment2"
        />
        
    </LinearLayout>


</LinearLayout>
MainActivity.java
package com.wxl.fragment;

import android.annotation.SuppressLint;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;


public class MainActivity extends FragmentActivity{

    @SuppressLint("NewApi") @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
       FragmentManager fragmentManager = getFragmentManager();
       FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
       
       Fragment1 fragment1 = new Fragment1();
       fragmentTransaction.add(R.id.main_linearLayout1, fragment1);
       fragmentTransaction.commit();
              
    }
}
技术分享
注意
本例子fragment2采用在activity_main.xml中直接添加,而fragment1采用在MainActivity.java中使用代码添加的。
采用xml方式的,即使用方式一添加Fragment的时候使用的是android.support.v4.app.Fragment;所有在Fragment2.java中导入包是:import android.support.v4.app.Fragment;
采用代码方式的,即使用方式二添加Fragment的时候使用的是android.app.Fragment;所有在Fragment1.java中导入包是:import android.app.Fragment;
而在本例子采用了两种方式,所有在MainActivity.java的Activity需要继承FragmentActivity(方式一需要使用),而方式无须继承FragmentActivity。
还得注意
inflater.inflate(R.layout.fragment1, container,false);
第三个参数必须为false,不能为true,否则出现以下提示错误
技术分享
MainActivity.java的Activity需要继承FragmentActivity(方式一需要使用),否则出现以下提示错误
技术分享

Android UI编程(7)——Fragment