首页 > 代码库 > Android Fragment

Android Fragment

fragment 是android3.0中就开始引入的一个碎片功能,这个主要是针对android平板电脑这种大屏幕来使用的,到了android4.0之后也就开始在手机上引入fragment,在之前没有fragment时,就是将UI元素和具体的Activity界面结合在一起,而我们一般是通过不同的Activity之间的跳转来实现不同界面的改变,这样一来不仅UI代码得不到重用而且不停的跳转也会导致一些混乱。fragment的引入正好将一个应用变为一个模块化和可重用的组件。因为每一个fragment有它自己的布局文件,而且不同的Activity可以使用相同的fragment。

 

fragment生命周期

先来看下面的图片

上面的图片我是别的网站截取过来的,从上图中我们可以很清楚的看到fragment生命周期和Activity是差不多相同的,记住Fragment是不能独立存在的,Fragment必须嵌入的Activty中,所以Fragment的生命周期是会受到Activity的生命周期的影响,当Activity暂停时那么所在的Fragment也会暂停,当Activity销毁时那么所在的Fragment也相应的销毁。但是当Activity运行之后即跑了onResume之后onPause之前,我们就可以单独对Fragment进行添加,删除,替换等一系列的操作。而一个Fragment可以添加在多个Activity中也可以是一个Activity中添加多个Fragment。

 

  如何创建一个简单的Fragment

要想创建一个Fragment就必须创建一个类继承Fragment或Fragment子类,这个Fragment代码写起来很像Activity,因为它们的生命周期都是差不多相同的,先来看一下继承Fragment类的代码:

package com.cheng.fragmentactivty;import android.annotation.SuppressLint;import android.annotation.TargetApi;import android.app.Activity;import android.app.Fragment;import android.os.Build;import android.os.Bundle;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;@SuppressLint("NewApi") public class FragmentA extends Fragment{    @Override    public void onAttach(Activity activity) {        // TODO Auto-generated method stub        super.onAttach(activity);    }    @Override    public void onCreate(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onCreate(savedInstanceState);    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,            Bundle savedInstanceState) {        // TODO Auto-generated method stub        return inflater.inflate(R.layout.fragment_a, container,false);    }    @Override    public void onActivityCreated(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onActivityCreated(savedInstanceState);    }    @Override    public void onStart() {        // TODO Auto-generated method stub        super.onStart();    }    @Override    public void onResume() {        // TODO Auto-generated method stub        super.onResume();    }    @Override    public void onPause() {        // TODO Auto-generated method stub        super.onPause();    }    @Override    public void onStop() {        // TODO Auto-generated method stub        super.onStop();    }    @Override    public void onDestroyView() {        // TODO Auto-generated method stub        super.onDestroyView();    }    @Override    public void onDestroy() {        // TODO Auto-generated method stub        super.onDestroy();    }}

 

上面的FragmentA就是继承自Fragment,实现了许多类似Activity的方法,比如onCreate,onPause,onDestory等,这些方法和Activity都是差不多的,我这里就不再说明了,我们平时在开发的过程中经常使用比较多的方法是:

onAttach()

这个是在刚刚开始添加Fragment与Activity关联的时候系统就会调用这个方法,具体当Activity里面调用setContentView()的方法的时候就调用这个onAttach()方法,主要是可以从这里获取到Activity的实例(注意看它的参数),当然在这里也可以使用getActivity()方法获取到它的activity的实例,这个我们后面会有说明的。

onCreate()

当创建Fragment时调用的方法在实现代码中, 应当初始化想要在fragment中保持的必要组件, 当fragment被暂停或者停止后可以恢复

onCreateView()

Fragment第一次绘制界面的时候系统就会调用这个方法,这个方法返回是一个view布局界面,看我上面贴的代码,onCreateView()方法中返回了

inflater.inflate(R.layout.fragment_a, container,false);这个界面,在onCreateView()方法中参数container指的是存放fragment布局layout中的viewgroup对象,简单点说就是当前fragment界面的父界面。我们来看看R.layout.fragment_a这个布局定义

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >        <Button         android:id="@+id/btn"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="set_text"        android:layout_centerInParent="true"        /></RelativeLayout>

这个布局我就添加了一个按钮,说明这个fragment界面就是显示一个按钮

onCreate()和onCreayeView()都是activity上调用setContentView()的时候调用的,也就是说我们的Activity中的onCreate()有可能还没有跑完,所以最好不要在两个方法中取操作Activity相关的view,不然可能会出现错误。

onActivityCreated()

这个方法也就是在activity中onCreate()方法完成之后被调用的,我们就可以在这个方法activity的UI操作

onPause()

这个和Activity中的onPause()意思是一样的,这里就不多介绍了

将Fragment添加到指定的Activity中

通过上面的方法一个fragment就简单创建好了,这个fragment只有一个button按钮,但是我们要记住一个fragment是不能单独存在的,它必须嵌入到某一个Activity中,那么如何把已经写好的fragment嵌入到指定的Activity中呢?这里有两种方法可以做到:

(1)在指定的Activity的layout文件中声明fragment

直接来看layout文件:

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context=".FragmentActivity" >    <fragment         android:name="com.cheng.fragmentactivty.FragmentA"        android:id="@+id/frag_a"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerHorizontal="true"        />    <TextView        android:id="@+id/txt"        android:layout_below="@id/frag_a"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="@string/hello_world"        android:layout_centerHorizontal="true"        android:layout_marginTop="20dp"         /></RelativeLayout>

在上面我们已经看到<fragment>标签,android:name属性指定在layout中实例化的Fragment类,当系统创建上述中的layout的时候就会实例化fragment标签中android:name指定的fragment,然后就调用onCreateView方法来获取到当前fragment所返回的layout,系统把fragment返回的layout插入<fragment>标签的位置直接替换掉了<fragment>标签。

注意:每一个fragment都需要指定唯一的标识,系统可以通过这个标识来进行一系列的事物操作,比如:添加,删除,替换等等

添加一个指定的标识很简单:

使用android:id属性提供唯一的ID,上面中已经使用

使用android:tag属性提供一个唯一的ID

如果以上的两个方法都没有提供,那么系统默认使用容器的ID,(好像在以后没有提供上面两个跑起来直接就崩溃的)

 

(2)在代码中手动将fragment添加到已经存在的viewGroup中

 先来看看我们Activity中layout布局做的改动:

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context=".FragmentActivity" >        <FrameLayout        android:id="@+id/content"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerHorizontal="true" />    <TextView        android:id="@+id/txt"        android:layout_below="@id/content"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="@string/hello_world"        android:layout_centerHorizontal="true"        android:layout_marginTop="20dp"         /></RelativeLayout>

看上面的layout,和之前那个不同的是我把<fragment>标签给去掉了,新添加了一个<FrameLayout>标签,这个标签的id为content,这个有作用的,我们来看下面的代码:

        FragmentA fragmentA=new FragmentA();        FragmentManager fragmentManager=getFragmentManager();        FragmentTransaction transaction=fragmentManager.beginTransaction();        transaction.add(R.id.content, fragmentA);        transaction.commit();

 

FragmentManager类是实现在Activity中管理fragment的,我们可以在Activity中通过getFragmentManager()方法得到一个实例,那么我们可以使用FragmentManager做以下事情:

 

使用findFragmentById()或者findFragmentByTag()方法来获取已经在布局中定义的fragment,这个看字面的意思就知道它们和Activity中的findViewById()是一个道理的。

 

使用addOnBackStack()方法从Activity后退栈中弹出fragment,就类似于我们的返回建,这个作用应该不是很大。在后面介绍有关于fragment退回栈的时候在说一下。

 

使用addOnBackStackChangedListerner来注册一个监听器来监听上面所说的后退栈的变化,用处很少

 

使用FragmentManager来打开一个FragmentTransaction事务,这个是经常使用的,很重用,如果学过数据库的同学应该知道什么是事务,我们可以通过FragmentTransaction事务对fragment进行添加,删除,替换等操作命令,如何获取一个FragmentTransaction对象,从上面的代码中可以看到使用fragmentManager.beginTransaction()可以获取一个FragmentTransaction事务,那么transaction.add(R.id.content, fragmentA);这句话就是将已经创建好的fragment添加到Activity中,那么这个添加到哪里呢?这个add第一个参数是R.id.content,这个就是我们上面的定义的<FrameLayout>标签,这句话表示的是将fragmentA添加到FrameLayout布局中,我们利用多次调用add()方法来添加fragment,这样fragment显示的顺序和添加的顺序是一样的,

这里要注意当我们使用FragmentTransaction事务进行想要的操作之后,我们要在最后对事务进行提交,不然你所作的操作就无法看到,如上代码中最后一句话transaction.commit();就是提交所有的操作。

 

在调用commit()之前, 你可能想调用 addToBackStack(),将事务添加到一个fragment事务的back stack. 这个back stack由activity管理, 并允许用户通过按下 BACK 按键返回到前一个fragment状态.这个就是前面所说的addOnBackStack()方法,其实这个理解起来也很简单就是将一个fragment保存到stack中,在用户按返回建的时候就会依次的退回到fragment界面。
 
这里有两点需要注意的:
 
1。我们在调用 commit() 并不立即执行事务.恰恰相反, 它将事务安排排期, 一旦准备好, 就在activity的UI线程上运行(主线程).如果有必要, 无论如何, 你可以从你的UI线程调用 executePendingTransactions() 来立即执行由commit()提交的事务. 但这么做通常不必要, 除非事务是其他线程中的job的一个从属.
 
2。你只能在activity保存它的状态(当用户离开activity)之前使用commit()提交事务,最好是在onResume()方法和onStop()方法之间来提交事务。如果你试图在那个点之后提交, 会抛出一个异常.这是因为如果activity需要被恢复, 提交之后的状态可能会丢失.对于你觉得可以丢失提交的状况, 使用 commitAllowingStateLoss().
 
好了,我们添加好fragment之后来看看界面是如何
 
 
就是这个,一个按钮和一个文本

 

 添加一个无UI的fragment

之前在onCreateView()方法中返回一个view:

    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,            Bundle savedInstanceState) {        // TODO Auto-generated method stub        return inflater.inflate(R.layout.fragment_a, container,false);    }

这个返回值也可以直接返回一个null,这个就表示添加了一个无UI的fragment,要把一个无UI的fragment添加到Activity中就不能在layout里面直接添加了,这个必须在Activity代码中使用add添加(为fragment提供一个唯一的字符串"tag", 而不是一个view ID).这么做添加了fragment, 但因为它没有关联到一个activity layout中的一个view, 所以不会接收到onCreateView()调用. 因此不必实现此方法。

 

Fragment交互

先来看一下如何在fragment代码里面获取到自己的view

    @Override    public void onActivityCreated(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onActivityCreated(savedInstanceState);        Button btn=(Button) getView().findViewById(R.id.btn);                btn.setOnClickListener(new OnClickListener() {                        @Override            public void onClick(View v) {                // TODO Auto-generated method stub                Toast.makeText(getActivity(), "OKOKOK", Toast.LENGTH_LONG).show();            }        });    }

 

看上面通过getView()方法来得到fragment的layout的(我们一般是在onActivityCreated()方法中取操作UI,这个是一个好习惯),这个代码看起来很容易吧,再来看看在fragment中操作activity中的view :

    @Override    public void onActivityCreated(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onActivityCreated(savedInstanceState);        Button btn=(Button) getView().findViewById(R.id.btn);                btn.setOnClickListener(new OnClickListener() {                        @Override            public void onClick(View v) {                // TODO Auto-generated method stub                Toast.makeText(getActivity(), "OKOKOK", Toast.LENGTH_LONG).show();                setText("OKOK");            }        });    }    private void setText(String text){        TextView textview=(TextView) getActivity().findViewById(R.id.txt);        textview.setText("OKOK");    }

我这里就是用了TextView textview=(TextView) getActivity().findViewById(R.id.txt);这个就可以获取到Activity中的view,现在我们也苦于反过来来,在Activity中获取fragment中的view

    private FragmentA fragmentA;    private FragmentManager fragmentManager;    private Button btn;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_fragment);        fragmentManager=getFragmentManager();        fragmentA=(FragmentA) fragmentManager.findFragmentById(R.id.fragment);        btn=(Button) fragmentA.getView().findViewById(R.id.btn);        btn.setOnClickListener(new OnClickListener() {                        @Override            public void onClick(View v) {                // TODO Auto-generated method stub                Toast.makeText(FragmentActivity.this, "koko", Toast.LENGTH_LONG).show();            }        });

 

我们先使用findFragmentById()找到指定的fragment,然后再通过fragment.getView()返回fragment的layout,这个代码很容易吧,不过要注意如果在fragment中设置了按钮的监听事件在Activity中又重新设置了,那么这个时候是以fragment中的为准。

其实在使用的过程中Fragment和Activity中是分开的,各自处理自己的view,那么比如在Fragment中button点击事件是如何响应到Activity中呢?一个必须好的方法就是在Fragment中定义一个接口,在Acticity中需要实现这个接口,

        public interface textListener{        public void textchanger(String text);    }

这个接口就是在Fragment类中定义的,

    private textListener listener;    @Override    public void onAttach(Activity activity) {        // TODO Auto-generated method stub        super.onAttach(activity);        try{            listener=(textListener) activity;        }catch(ClassCastException e){            throw new ClassCastException(activity.toString() + " must implement textlistener");        }            }

 

fragmentA的 onAttach() 回调方法(当添加fragment到activity时由系统调用) 通过将作为参数传入onAttach()的Activity做类型转换来实例化一个textListener实例.如果activity没有实现接口, fragment会抛出 ClassCastException 异常. 正常情形下, listener成员会保持一个到activity的textListener实现的引用, 因此fragment A可以通过调用在OnArticleSelectedListener接口中定义的方法分享事件给activity

public class FragmentActivity extends Activity implements textListener{    private FragmentA fragmentA;    private FragmentManager fragmentManager;    private Button btn;    private TextView textView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_fragment);                textView=(TextView) findViewById(R.id.txt);            }    @Override    public void textchanger(String text) {        // TODO Auto-generated method stub        textView.setText(text);    }    

这个就是Activity,我们可以看到这个Activity实现了textListner的接口,在回调textChanger()方法中我们设置text文本显示,这里应该很容易理解

 

addToBackStack()和replace()方法

之前有简单的介绍过addToBackStack(),这个方法就把移除的Fragment放到Activity的stack栈中,当我们按返回建的时候就会依次的返回到位于在stack栈顶中的fragment,如果没有调用 addToBackStack(), 那么当事务提交后, 那个fragment会被销毁,并且用户不能导航回到它. 有鉴于此, 当移除一个fragment时,如果调用了 addToBackStack(), 那么fragment会被停止, 如果用户导航回来,它将会被恢复。replace()就是一个替换当前的fragment,先来看一下代码:

 

public class FragmentActivity extends Activity implements textListener{    private FragmentA fragmentA;    private FragmentB fragmentB;    private FragmentManager fragmentManager;    private Button btn;    private TextView textView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_fragment);                textView = (TextView) findViewById(R.id.txt);        btn=(Button) findViewById(R.id.setfragment);                fragmentA = new FragmentA();        fragmentB = new FragmentB();                fragmentManager = getFragmentManager();                FragmentTransaction transaction = fragmentManager.beginTransaction();        transaction.add(R.id.content, fragmentA);        transaction.commit();                btn.setOnClickListener(new OnClickListener() {                        @Override            public void onClick(View v) {                // TODO Auto-generated method stub                FragmentTransaction transaction = fragmentManager.beginTransaction();                transaction.replace(R.id.content, fragmentB);                transaction.addToBackStack(null);                transaction.commit();            }        });            }

 

从代码中我们看到在刚刚开始的时候显示的FragmentA,在点击按钮之后我们使用了replace()把FragmentA换成了FragmentB,这个时候我们还可以看到代码里面在commit之前使用了 transaction.addToBackStack(null);如果我们点击按钮切换到FragmentB的时候再按返回建这个时候又回到FragmentA中而不是直接退出Activity,transaction.replace(R.id.content, fragmentB);大家还记得R.id.content是哪个id么?我这里在贴一次代码吧:

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context=".FragmentActivity" >            <FrameLayout        android:id="@+id/content"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerHorizontal="true" />            <TextView        android:id="@+id/txt"        android:layout_below="@id/content"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="@string/hello_world"        android:layout_centerHorizontal="true"        android:layout_marginTop="20dp"         />        <Button         android:id="@+id/setfragment"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerHorizontal="true"        android:layout_below="@id/txt"        android:text="replace"        /></RelativeLayout>

这个是Activity的layout

 

 在Fragment代码中添加ActionBar

ActionBar我之前有一边文章也介绍过了,那么我们利用在Fragment中添加的ActionBar显示效果和Activity一样显示效果一样,在Fragment中可以通过实现onCreateOptionMenu(),但是要使用这个方法来能调用,我们必须在Fragment中的onCreate()方法中调用setHasOptionsMenu()来指出fragment愿意添加item到选项菜单(否则, fragment将接收不到对 onCreateOptionsMenu()的调用),随后从fragment添加到Option菜单的任何项,都会被追加到现有菜单项的后面.当一个菜单项被选择, fragment也会接收到 对 onOptionsItemSelected() 的回调.也可以在你的fragment layout中通过调用 registerForContextMenu() 注册一个view来提供一个环境菜单.当用户打开环境菜单, fragment接收到一个对 onCreateContextMenu() 的调用.当用户选择一个项目, fragment接收到一个对onContextItemSelected() 的调用.还有一个是我们要注意在Fragment的onOptionsItemSelected()可以接受菜单选项的点击事件但是这个最开始还是在Activity中先接受到,只有Activity中没有做处理时才会调用到Fragment中的onOptionsItemSelected()方法,来看一下代码吧:

 

package com.cheng.fragmentactivty;import android.annotation.SuppressLint;import android.annotation.TargetApi;import android.app.ActionBar;import android.app.Activity;import android.app.Fragment;import android.os.Build;import android.os.Bundle;import android.view.LayoutInflater;import android.view.Menu;import android.view.MenuInflater;import android.view.MenuItem;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;@SuppressLint("NewApi") public class FragmentA extends Fragment{    private textListener listener;        @Override    public void onAttach(Activity activity) {        // TODO Auto-generated method stub        super.onAttach(activity);        try{            listener=(textListener) activity;        }catch(ClassCastException e){            throw new ClassCastException(activity.toString() + " must implement textlistener");        }            }    @Override    public void onCreate(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onCreate(savedInstanceState);         setHasOptionsMenu(true);    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,            Bundle savedInstanceState) {        // TODO Auto-generated method stub        return inflater.inflate(R.layout.fragment_a, container,false);    }    @Override    public void onActivityCreated(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onActivityCreated(savedInstanceState);        Button btn=(Button) getView().findViewById(R.id.btn);                btn.setOnClickListener(new OnClickListener() {                        @Override            public void onClick(View v) {                // TODO Auto-generated method stub                Toast.makeText(getActivity(), "OKOKOK", Toast.LENGTH_LONG).show();                                listener.textchanger("OKOKOK");            }        });    }    @Override    public void onStart() {        // TODO Auto-generated method stub        super.onStart();    }    @Override    public void onResume() {        // TODO Auto-generated method stub        super.onResume();    }    @Override    public void onPause() {        // TODO Auto-generated method stub        super.onPause();    }    @Override    public void onStop() {        // TODO Auto-generated method stub        super.onStop();    }    @Override    public void onDestroyView() {        // TODO Auto-generated method stub        super.onDestroyView();    }    @Override    public void onDestroy() {        // TODO Auto-generated method stub        super.onDestroy();    }        public interface textListener{        public void textchanger(String text);    }    @Override    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {        // TODO Auto-generated method stub        super.onCreateOptionsMenu(menu, inflater);//        menu.add("Menu 1a").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);//        menu.add("Menu 2a").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);        inflater.inflate(R.menu.gmail, menu);            }    @Override    public boolean onOptionsItemSelected(MenuItem item) {        // TODO Auto-generated method stub        return super.onOptionsItemSelected(item);    }        }


上面的代码中看先看onCreate()方法,调用了setHasOptionsMenu(true);如果不调用的话,Fragment中onCreateOptionsMenu()方法就没有调用,再来看看我们的onCreateOptionsMenu()里面使用了inflater.inflate(R.menu.gmail, menu);将布局写进去,同时也可以想注视那两次话这样写,剩下的onOptionsItemSelected()点击事件就很简单了,在这里也不必说了,先来看看图片

 

代码地址:http://files.cnblogs.com/itchq/FragmentActivty.zip