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();就是提交所有的操作。
添加一個無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()點擊事件就很簡單了,在這里也不必說了,先來看看圖片


