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()點擊事件就很簡單了,在這里也不必說了,先來看看圖片