Fragment 第三步——管理Fragment

一、概述

 1、FragmentManager

要管理activity中的fragments,你就需要使用FragmentManager。通過getFragmentManager()或getSupportFragmentManager()獲得 
常用的方法有:

[java]  view plain  copy
  1. manager.findFragmentById();  //根據ID來找到對應的Fragment實例,主要用在靜態添加fragment的佈局中,因爲靜態添加的fragment纔會有ID  
  2. manager.findFragmentByTag();//根據TAG找到對應的Fragment實例,主要用於在動態添加的fragment中,根據TAG來找到fragment實例  
  3. manager.getFragments();//獲取所有被ADD進Activity中的Fragment  

2、FragmentTransaction

一般用來對當前的Fragment進行管理,包括add,replace,remove;
常用的針對Fragment的方法有:

[java]  view plain  copy
  1. //將一個fragment實例添加到Activity的最上層  
  2. add(int containerViewId, Fragment fragment, String tag);  
  3. //將一個fragment實例從Activity的fragment隊列中刪除  
  4. remove(Fragment fragment);  
  5. //替換containerViewId中的fragment實例,注意,它首先把containerViewId中所有fragment刪除,然後再add進去當前的fragment  
  6. replace(int containerViewId, Fragment fragment);  
還有hide()、show()、detach()、attach()這些函數,我們下篇再講,這節先對Fragment的用法有一個初步瞭解;

二、add()、replace()、remove()使用方法示例

下面就通過例子來看看以上幾個函數的使用方法吧。 
效果圖如下:

  • 點擊「ADD Fragment1」,在將Fragment1添加到Activity的container中;
  • 點擊「ADD Fragment2」,將Fragment2添加到Activity的container中;
  • 點擊「Remove Fragment2」,將Fragment2的實例從container中移除,移除之後,就顯示出其下方的fragment1的視圖出來了。
  • 再點擊」replace Fragment1」,將container中的視圖移除,然後添加上fragment2的視圖。 


那現在我們從頭開始構建這個工程:

1、新建兩個fragment1.xml 和 fragment2.xml:

從效果圖中也可以看出,這兩個XML什麼都沒有,只是通過背景色和文字來區別當前是哪個Fragment的XML佈局文件而已,他們的佈局代碼如下:

fragment1.xml:

[html]  view plain  copy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent"  
  4.     android:background="#ff00f0"  
  5.     android:orientation="vertical" >  
  6.       
  7.     <TextView  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:text="This is fragment 1"  
  11.         android:textColor="#000000"  
  12.         android:textSize="25sp" />  
  13.   
  14. </LinearLayout>  
fragment2.xml:
[html]  view plain  copy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent"  
  4.     android:background="#ffff00"  
  5.     android:orientation="vertical" >  
  6.       
  7.     <TextView  
  8.         android:id="@+id/fragment2_tv"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:text="This is fragment 2"  
  12.         android:textColor="#000000"  
  13.         android:textSize="25sp" />  
  14.       
  15. </LinearLayout>  

2、建立對應的Fragment類:Fragment1和Fragment2

Fragment1:

[java]  view plain  copy
  1. import android.os.Bundle;  
  2. import android.support.v4.app.Fragment;  
  3. import android.view.LayoutInflater;  
  4. import android.view.View;  
  5. import android.view.ViewGroup;  
  6.   
  7. public class Fragment1 extends Fragment {  
  8.     @Override  
  9.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  10.             Bundle savedInstanceState) {  
  11.         return inflater.inflate(R.layout.fragment1, container, false);  
  12.     }  
  13.   
  14. }  
與上一篇一樣,也只是在onCreateView()時返回對應的佈局。同樣,Fragment2的定義如下:
[java]  view plain  copy
  1. import android.os.Bundle;  
  2. import android.support.v4.app.Fragment;  
  3. import android.view.LayoutInflater;  
  4. import android.view.View;  
  5. import android.view.ViewGroup;  
  6.   
  7. public class Fragment2 extends Fragment {  
  8.     @Override  
  9.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  10.                              Bundle savedInstanceState) {  
  11.         return inflater.inflate(R.layout.fragment2, container, false);  
  12.     }  
  13. }  

3、MainActivity的佈局

從上面的的效果圖中也可以看出大概的佈局,首先是三個Button,最下方是一個FrameLayout佈局,它是用來做爲container動態盛裝fragment的;它就像是一個佔位符,你設置多大,它其中的fragment就最大能有多大。記住,fragment也是Activity中的一個普通控件而已,只不過它可以像Activity一樣用於顯示的同時還能用來盛裝其它控件!作爲fragment的容器,即可以用FrameLayout也可以用LinearLayout或者RelativeLayout,都是一樣的。activity_main.xml的佈局代碼如下:

[html]  view plain  copy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent"  
  4.     android:orientation="vertical">  
  5.   
  6.     <Button  
  7.         android:id="@+id/btn_add_frag1"  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:text="ADD  Fragment1" />  
  11.   
  12.     <Button  
  13.         android:id="@+id/btn_add_frag2"  
  14.         android:layout_width="match_parent"  
  15.         android:layout_height="wrap_content"  
  16.         android:text="ADD  Fragment2" />  
  17.   
  18.     <Button  
  19.         android:id="@+id/btn_remove_frag2"  
  20.         android:layout_width="match_parent"  
  21.         android:layout_height="wrap_content"  
  22.         android:text="Remove  Fragment2" />  
  23.   
  24.     <Button  
  25.         android:id="@+id/btn_repalce_frag1"  
  26.         android:layout_width="match_parent"  
  27.         android:layout_height="wrap_content"  
  28.         android:text="replace  Fragment1" />  
  29.   
  30.     <FrameLayout  
  31.         android:id="@+id/fragment_container"  
  32.         android:layout_width="match_parent"  
  33.         android:layout_height="match_parent"/>  
  34.   
  35. </LinearLayout>  

4、MainActivity的實現:

(1)首先,先寫一個添加fragment到Activity中的函數:

[java]  view plain  copy
  1. private void addFragment(Fragment fragment, String tag) {  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     FragmentTransaction transaction = manager.beginTransaction();  
  4.     transaction.add(R.id.fragment_container, fragment, tag);  
  5.     transaction.commit();  
  6. }  
  • 先是傳進來兩個參數,一個是要添加的fragment實例,另一個是一個TAG
  • 至於FragmentManager、FragmentTransaction就沒什麼好講的,這是這麼個流程,要獲取它們的實例就得調用這些函數。
  • 然後是add(R.id.fragment_container, fragment, tag)函數:第一個參數是要將fragment盛裝的container,即我們上面的FrameLayout!第三個參數是tag,當傳進去這個TAG,它就會跟這個fragment關聯起來,當我們通過findFragmentByTag()時,根據這個TAG就能找到這個Fragment實例。進而對它進行操作,比如,我們下面的remove();
  • 在通過transaction對Fragment操作完以後,一定要記得調用transaction.commit(),這樣纔會將操作提交到系統中,這裏的代碼纔會最終起作用。

有沒有覺得這個流程挺像數據庫的回滾操作!對,其實就是這樣的,這裏其實就是一個針對Fragment的回滾操作,首先通過beginTransaction()來定義回滾的開始,然後通過transaction對Fragment進行一系列操作(我們這裏只是進行了ADD,其實可以做一串操作),等操作完了利用commit()提交。這裏就先初步講到這,下節會細講有關transaction的回滾過程。
(2)添加Fragment1和Fragment2:
好了,上面我們寫好了一個函數addFragment(Fragment fragment, String tag),下面我們就要通過這個函數來添加Fragment1和Fragment2的實例了。
當點擊「ADD Fragment1」按鈕時:

[java]  view plain  copy
  1. Fragment1 fragment1 = new Fragment1();  
  2. addFragment(fragment1, "fragment1");  
當點擊「ADD Fragment2」按鈕時:
[java]  view plain  copy
  1. Fragment2 fragment2 = new Fragment2();  
  2. addFragment(fragment2, "fragment2");  
這裏需要注意的是,當我們添加每個Fragment實例時,都傳進去一個對應的TAG,fragment1對應的是「fragment1」,fragment2對應的是「fragment2」,通過這些TAG,我們就可以通過findFragmentByTag()來獲取它們了。
(3)RemoveFragment2:
下面就是當點擊「RemoveFragment2」按鈕時的代碼操作了:
[java]  view plain  copy
  1. private void removeFragment2() {  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     Fragment fragment = manager.findFragmentByTag("fragment2");  
  4.     FragmentTransaction transaction = manager.beginTransaction();  
  5.     transaction.remove(fragment);  
  6.     transaction.commit();  
  7. }  
首先是通過
[java]  view plain  copy
  1. Fragment fragment = manager.findFragmentByTag("fragment2");  
找到我們當時ADD進Activity的fragment2實例,然後通過transaction.remove(fragment);將它刪除;從效果圖中可以看到,由於我們移除了fragment2的實例,而又由於fragment2是在界面最上方的,所以把它刪除了之後,自然就剩下了fragment1在最上方了,所以我們就看到了fragment1的界面。那如果我們移除的是fragment1呢?答案是界面沒有任何變化!因爲fragment1的實例是在fragment2的下方的,我們是根本看不到它的。
(4)ReplaceFragment1:

最後一個操作:ReplaceFragment1:

咱們先回到上面的RemoveFragment2操作,在RemoveFragment2之後,要知道我們的Activity的ADD隊列中,就只有fragment1了。知道這一點之後,咱們看下面ReplaceFragment1的代碼:

[java]  view plain  copy
  1. private void replaceFragment1() {  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     Fragment2 fragment2 = new Fragment2();  
  4.     FragmentTransaction transaction = manager.beginTransaction();  
  5.     transaction.replace(R.id.fragment_container, fragment2);  
  6.     transaction.commit();  
  7. }  
這裏有個注意的問題:
[java]  view plain  copy
  1. transaction.replace(R.id.fragment_container, fragment2);  
這裏replace傳進去的第一個參數是容器ID,第二個參數是要新增的fragment。既然要replace(),那它是針對哪個fragment進行replace()呢?怎麼沒有指定要替換的fragment!爲什麼這裏的第一個參數是盛裝Activity中所有Fragment的container的ID呢?沒錯,這裏的replace操作會把這個cotainer中所有fragment清空!!!!然後再把fragment2添加進去!
從上面的的講解,大家可能也已經覺查到FragmentTransaction的Add()操作是維持着一個隊列的,在這個隊列中,根據ADD進去的先後順序形成了一個鏈表,我們上面的操作在這個列表中的形式變化如下圖所示:

源碼在文章最底部給出

到這裏add,replace,remove的使用方法就講完了,但這裏有個問題,必須根大家講一下:我們上面說過replace操作會把container中的所有fragment全部刪除,然後再將指定的fragment添加進去!但Android在實現時出現了BUG!在replace()時,並不能把以前所有Fragment清空,就因爲這個系統工程產了BUG就會導致add()和Replace()不能共用!關於add()和Replace()不能共用的問題,我們會在下篇再講。下面先給大家說說有關回滾的問題。

三、有關回滾——FragmentTransaction

1、FragmentTransaction事務回滾使用方法:

上部分,我們講了有關添加、刪除Fragment的操作,想將上一次commit的操作返回時,要怎麼做呢。這就需要FragmentTransaction的回滾功能了。 
要使用回滾功能,只需要要使用下面兩個代碼: 
在transaction.commit()之前,使用addToBackStack()將其添加到回退棧中。

[java]  view plain  copy
  1. transaction.addToBackStack(String tag);  
在需要回退時,使用popBackStack()將最上層的操作彈出回退棧。
[java]  view plain  copy
  1. manager.popBackStack();  
這裏的popBackStack()是彈出默認的最上層的棧頂內容。
當棧中有多層時,我們可以根據id或TAG標識來指定彈出到的操作所在層。函數如下:
[java]  view plain  copy
  1. void popBackStack(int id, int flags);  
  2. void popBackStack(String name, int flags);  
其中
  • 參數int id是當提交變更時transaction.commit()的返回值。
  • 參數string name是transaction.addToBackStack(String tag)中的tag值;
  • 至於int flags有兩個取值:0或FragmentManager.POP_BACK_STACK_INCLUSIVE;
  • 當取值0時,表示除了參數一指定這一層之上的所有層都退出棧,指定的這一層爲棧頂層; 
  • 當取值POP_BACK_STACK_INCLUSIVE時,表示連着參數一指定的這一層一起退出棧; 
  • 除了這幾個函數,還有下面幾個函數:有關他們的使用,我們在這小部分結尾時會提到
[java]  view plain  copy
  1. popBackStackImmediate()  
  2. popBackStackImmediate(String tag)  
  3. popBackStackImmediate(String tag, int flag)  
  4. popBackStackImmediate(int id, int flag)  
下面我們就通過一個例子來講解一下有關回退棧中的操作過程:

先看下效果圖:


整體流程是這樣的:
1、逐個將Fragment1,2,3,4添加到窗口中,在添加時,每添加一個Fragment要利用transaction的addToBackStack將此次操作加入到回退棧中。
2、然後點擊"PopBackStack"方法,將棧頂最上層的操作回退。退將最後一次添加回退出來,顯示Fragment3.
3、點擊「ADD Fragment4」將棧還原到1,2,3,4依次ADD進棧的狀態,即操作1完成後的棧狀態,然後點擊「BackToStack2_0」,其實調用的方法是:
[java]  view plain  copy
  1. manager.popBackStack("fragment2",0);//方法一,通過TAG回退  
從這裏可以看出,要回退到添加ADD Fragment2的狀態,注意最後一個參數,這裏設爲0,表明,要回退ADD Fragment2的之後的操作,將ADD Fragment2的操作置爲棧頂。從效果圖中也可以看出,點擊後的視圖在Fragment2的位置
4、最後仍然是先點擊"Add Fragment3"和"ADD Fragment4",將棧還原到操作1完成後的棧狀態。然後點擊「BackToStack2_INCLUSIVE」;其調用的方法是:
[java]  view plain  copy
  1. manager.popBackStack("fragment2",FragmentManager.POP_BACK_STACK_INCLUSIVE);//方法一,通過TAG回退  
這裏與上面的主要不同點在於第二個參數,這裏設置爲POP_BACK_STACK_INCLUSIVE,即在出棧時連帶ADD Fragment2的操作一塊出棧,放在棧頂的是ADD Fragment1的操作,所以放在界面上就是顯示的是Fragment1的視圖。
下面我們看看具體實現:
(1)、首先,與上部分一樣,先添加四個Fragment,並用背景色和文字來區分。這部分代碼我們就不講了。
主要看看點擊按鈕的代碼處理方法。
(2)、首先是添加Fragment1:

[java]  view plain  copy
  1. Fragment1 fragment1 = new Fragment1();  
  2. stackID1 = addFragment(fragment1,"fragment1");  
其中:

[java]  view plain  copy
  1. private int stackID1,stackID2,stackID3,stackID4;  
[java]  view plain  copy
  1. private int addFragment(Fragment fragment,String stackName){  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     FragmentTransaction transaction = manager.beginTransaction();  
  4.     transaction.add(R.id.fragment_container,fragment);  
  5.     transaction.addToBackStack(stackName);  
  6.     return transaction.commit();  
  7. }  
首先,這裏的stackID1,stackID2,stackID3,stackID4是用來保存每次commit()後返回的Transaction的ID值。在void popBackStack(int id, int flags);時,其中的參數id就是這個值
然後在每次ADD操作後,利用addToBackStack(string name)將每次ADD操作添加進回退棧中;
同樣,添加Fragment2的代碼如下,添加Fragment3,Fragment4的方法同理
[java]  view plain  copy
  1. Fragment2 fragment2 = new Fragment2();  
  2. stackID2 = addFragment(fragment2,"fragment2");  
(3)、然後是回退棧頂內容:
[java]  view plain  copy
  1. private void popBackStack(){  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     manager.popBackStack();  
  4. }  
(4)、接着是點擊BackToFrag2_0按鈕的內容, 這裏有兩種方法實現,一種是指定TAG,一種是利用Commit()返回的ID
[java]  view plain  copy
  1. private void popBackStackToFrag2_0(){  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     manager.popBackStack("fragment2",0);//方法一,通過TAG回退  
  4.     // manager.popBackStack(stackID2,0);//方法二,通過Transaction ID回退  
  5. }  
(5)、最後是點擊BackToFrag2_INCLUSIVE按鈕的代碼:
[java]  view plain  copy
  1. private void popBackStackToFrag2_Inclusive(){  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     manager.popBackStack("fragment2",FragmentManager.POP_BACK_STACK_INCLUSIVE);//方法一,通過TAG回退  
  4. // manager.popBackStack(stackID2,FragmentManager.POP_BACK_STACK_INCLUSIVE);//方法二,通過Transaction ID回退  
  5. }  
好了,到這裏,有關回滾的基本使用就結束了,需要要注意的是:
使用popBackStack()來彈出棧內容的話,調用該方法後會將事物操作插入到FragmentManager的操作隊列,只有當輪詢到該事物時才能執行。如果想立即執行事物的話,需要使用下面幾個對應的方法:
[java]  view plain  copy
  1. popBackStackImmediate()  
  2. popBackStackImmediate(String tag)  
  3. popBackStackImmediate(String tag, int flag)  
  4. popBackStackImmediate(int id, int flag)  

2、回退棧(back stack)狀態改變監聽

FragmentManager還爲我們提供了監控回退棧狀態改變的方法:

[java]  view plain  copy
  1. FragmentManager::addOnBackStackChangedListener(listener);//添加監聽器  
  2. FragmentManager::removeOnBackStackChangedListener(listener);//移除監聽器  
通過添加監聽器,就可以在回退棧內容改變時,及時收到通知;
我們在上面代碼的基礎上,在MainAcitivy中爲FragmentManager添加一個監聽器,當回退棧狀態改變時,打出一個LOG。具體實現如下:
(1)、OnCreate()中:
爲fragmentManger添加一個監聽器:
[java]  view plain  copy
  1. FragmentManager manager = getSupportFragmentManager();  
  2. listener = new FragmentManager.OnBackStackChangedListener() {  
  3.       
  4.     @Override  
  5.     public void onBackStackChanged() {  
  6.         // TODO Auto-generated method stub  
  7.         Log.d("qijian","backstack changed");  
  8.     }  
  9. };  
  10. manager.addOnBackStackChangedListener(listener);  
(2)、當onDestory()中將監聽器remove掉:
[java]  view plain  copy
  1. protected void onDestroy() {  
  2.     // TODO Auto-generated method stub  
  3.     super.onDestroy();  
  4.     FragmentManager manager = getSupportFragmentManager();  
  5.     manager.removeOnBackStackChangedListener(listener);  
  6. }  

大家一定要注意,不管是這裏的回退棧的監聽還是其它的監聽器,在頁面對應的銷燬時,都要記得remove掉,不然會造成頁面不釋放,這也是造成OOM的問題之一。

這樣當回退棧內容出現變動時,變會打LOG出來,如圖:


源碼在文章底部給出

3、Transaction事務回退的原則

這裏我們着重講一下,回退是以commit()提交的一次事務爲單位的,而不是以其中的add,replace等等操作爲單位回退的,即,如果我們在一次提交是添加了fragment2,fragment3,fragment4,那麼回退時,會依據添加時的順序,將它們一個個刪除,返回到沒有添加fragment4,fragment3,fragment2的狀態。
下面我們仍然寫一個例子來講明一下事務的回退原則,效果圖如下:


  • 1、首先,添加Fragment1,提交一次事務
  • 2、然後,一次添加Fragment2,Fragment3,Fragment4,然後提交一次事務
  • 3、利用popBackStack()將頂層事務出棧,可以看到把Fragment2,Fragment3,Fragment4一次出棧,界面顯示在了Fragment1的位置,這就充分說明了,回滾是以提交的事務爲單位進行的!

下面是代碼實現部分:
1、同樣,新建四個Fragment,分別利用背景色和文字來表明之間的不同。
2、然後添加Fragment1的代碼如下:

[java]  view plain  copy
  1. private void addFragment1() {  
  2.     Fragment1 fragment1 = new Fragment1();  
  3.     FragmentManager manager = getSupportFragmentManager();  
  4.     FragmentTransaction transaction = manager.beginTransaction();  
  5.     transaction.add(R.id.fragment_container, fragment1);  
  6.     transaction.addToBackStack("fragment1");  
  7.     transaction.commit();  
  8. }  
3、然後是添加其它三個Fragment的代碼如下:
[java]  view plain  copy
  1. private void addOtherFragments() {  
  2.     Fragment2 fragment2 = new Fragment2();  
  3.     Fragment3 fragment3 = new Fragment3();  
  4.     Fragment4 fragment4 = new Fragment4();  
  5.     FragmentManager manager = getSupportFragmentManager();  
  6.     FragmentTransaction transaction = manager.beginTransaction();  
  7.     transaction.add(R.id.fragment_container, fragment2);  
  8.     transaction.add(R.id.fragment_container, fragment3);  
  9.     transaction.add(R.id.fragment_container, fragment4);  
  10.     transaction.addToBackStack("other fragments");  
  11.     transaction.commit();  
  12. }  
再一次重申,從代碼中也可以看到,是依次將Fragment2、Fragment3、Fragment4加入容器之後,再提交一次事務,所以下面回滾時,會將他們反順序的依次刪除。即remove(fragment4)、remove(fragment3)、remove(fragment2)
4、點擊popBackStack按鈕時的代碼
[java]  view plain  copy
  1. private void popBackStack() {  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     manager.popBackStack();  
  4. }  


一、hide()、show()

1、基本使用

這兩個函數的功能非常簡單, 

[java]  view plain  copy
  1. public FragmentTransaction hide(Fragment fragment);//將指定的fragment隱藏不顯示   
  2. public FragmentTransaction show(Fragment fragment);//將以前hide()過的fragment顯示出來   
先看下面的效果圖:

  • 首先,依次添加fragment1,fragment2,fragment3
  • 然後點擊」frag3 hide」,將fragment3隱藏不顯示,所以就顯示出來它的下一層fragment2的視圖
  • 然後再點擊「frag3 show」,將fragment3重新顯示出來
  • 然後點擊「frag2 hide」按鈕,將fragment2隱藏,但是由於fragment3覆蓋在fragment2之上,fragment2隱藏之後對fragment3沒有任何影響,所以在視圖上看不到任何效果。
  • 這時候,我們再點擊「hide frag3」,將fragment3隱藏起來,這時候,由於fragment3和fragment2都隱藏了,所以顯示的就是fragment1的視圖。
  • 最後,點擊「frag2 show」將fragment2顯示出來 

代碼如下:
(1)、同樣是新建三個fragment,命名爲Fragment1,Fragment2,Fragment3,同樣是用背景色和文字來區別;
(2)、然後是點擊「add frag1」按鈕的代碼

[java]  view plain  copy
  1. Fragment1 fragment1 = new Fragment1();  
  2. addFragment(fragment1, "fragment1");  
其中:
[java]  view plain  copy
  1. private void addFragment(Fragment fragment, String tag) {  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     FragmentTransaction transaction = manager.beginTransaction();  
  4.     transaction.add(R.id.fragment_container,fragment, tag);  
  5.     transaction.addToBackStack(tag);  
  6.     transaction.commit();  
  7. }  
這個函數已經在前面幾章用過N多次了,就不再講了。
(3)、frag3 hide的代碼:
[java]  view plain  copy
  1. FragmentManager manager = getSupportFragmentManager();  
  2. Fragment fragment = manager.findFragmentByTag("fragment3");  
  3. FragmentTransaction transaction = manager.beginTransaction();  
  4. transaction.hide(fragment);  
  5. transaction.addToBackStack("hide fragment3");  
  6. transaction.commit();  
也沒什麼難度,跟前面幾篇不一樣的地方就是調用了transaction.hide(fragment);函數;
(4)、frag3 show的代碼:
[java]  view plain  copy
  1. FragmentManager manager = getSupportFragmentManager();  
  2. Fragment fragment = manager.findFragmentByTag("fragment3");  
  3. FragmentTransaction transaction = manager.beginTransaction();  
  4. transaction.show(fragment);  
  5. transaction.addToBackStack("show fragment3");  
  6. transaction.commit();  
這裏也基本上與以前的操作代碼都一樣,只是使用了transaction.show(fragment);函數

2、在實戰中的運用方法

如果我們使用replace來切換頁面,那麼在每次切換的時候,Fragment都會重新實例化,重新加載一邊數據,這樣非常消耗性能和用戶的數據流量。
這是因爲replace操作,每次都會把container中的現有的fragment實例清空,然後再把指定的fragment添加進去,就就造成了在切換到以前的fragment時,就會重新實例會fragment。
正確的切換方式是add(),切換時hide(),add()另一個Fragment;再次切換時,只需hide()當前,show()另一個。
這樣就能做到多個Fragment切換不重新實例化:(基本算法如下)

[java]  view plain  copy
  1. public void switchContent(Fragment from, Fragment to) {  
  2.     if (!to.isAdded()) {    // 先判斷是否被add過  
  3.         transaction.hide(from).add(R.id.content_frame, to).commit(); // 隱藏當前的fragment,add下一個到Activity中  
  4.     } else {  
  5.         transaction.hide(from).show(to).commit(); // 隱藏當前的fragment,顯示下一個  
  6.     }  
  7. }  
大家可能覺得這裏有個問題,如果我們要show()的fragment不在最頂層怎麼辦?如果不在ADD隊列的隊首,那顯然show()之後是不可見的;那豈不影響了APP邏輯。大家有這個想法是很棒的,但在APP中不存在這樣的情況,因爲我們的APP的fragment是一層層ADD進去的,而且我們的fragment實例都是唯一的,用TAG來標識,當退出的時候也是一層層剝離的,所以當用戶的動作導致要添加某個fragment時,那說明這個fragment肯定是在棧頂的。

二、detach()、attach()

這兩個函數的聲明如下:

[java]  view plain  copy
  1. public FragmentTransaction detach(Fragment fragment);  
  2. public abstract FragmentTransaction attach(Fragment fragment);  
detach(): 會將view與fragment分離,將此將view從viewtree中刪除!而且將fragment從Activity的ADD隊列中移除!所以在使用detach()後,使用fragment::isAdded()返回的值是false;但此fragment實例並不會刪除,此fragment的狀態依然保持着使用,所以在fragmentManager中仍然可以找到,即通過FragmentManager::findViewByTag()仍然是會有值的。 
attach(): 顯然這個方法與detach()所做的工作相反,它一方面利用fragment的onCreateView()來重建視圖,一方面將此fragment添加到ADD隊列中;這裏最值得注意的地方在這裏:由於是將fragment添加到ADD隊列,所以只能添加到列隊頭部,所以attach()操作的結果是,最新操作的頁面始終顯示在最前面!這也就解釋了下面的例子中,爲了fragment2 detach()後,當再次attach()後,卻跑到了fragment3的前面的原因。還有,由於這裏會將fragment添加到Activity的ADD隊列中,所以在這裏調用fragment::isAdded()將返回True; 
下面用一個例子來講講,有關這上面所講解的知識,效果圖如下:

  • (1)、同樣,先依次添加Fragment1,Fragment2,Fragment3
  • (2)、然後點擊「frag3 detach」,將fragment3的View視圖刪除,然後從ADD隊列中將fragment移除。之後點擊「fragment is added?」根據TOAST可以看出,fragment::isAdded()函數返回值是false;
  • (3)、然後點擊「frag3 attach」,將fragment重新與Activity綁定,它有兩個動作,一方面重建fragment視圖,一方面將fragment添加到Activity的ADD隊列中;所以這時候點擊「fragment is added?」,fragment::isAdded()函數返回值是true;
  • (4)、然後點擊「frag2 detach」,由於fragment2在fragment3之下,所以給fragment2使用detach,在界面上看不出任何效果。
  • (5)、但當點擊「frag2 attach」時,問題出現了,由於attach()會做兩件事,一方面重建fragment視圖,一方面將fragment添加到Activity的ADD隊列中;由於是ADD隊列,所以肯定添加的位置肯定在隊首;所以fragment2就顯示在了最上方,把fragment3蓋住了,這就是爲什麼在點擊「frag2 attach」之後,卻可以看到fragment2的視圖的原因! 
好了,下面就是代碼部分了,這部分代碼是在上一部分的上面添加了幾個按鈕而來的,直接看按鈕點擊時的代碼操作:

1、點擊frag3 detach按鈕的代碼

[java]  view plain  copy
  1. FragmentManager manager = getSupportFragmentManager();  
  2. Fragment fragment = manager.findFragmentByTag("fragment3");  
  3. FragmentTransaction transaction = manager.beginTransaction();  
  4. transaction.detach(fragment);  
  5. transaction.addToBackStack("detach fragment3");  
  6. transaction.commit();  
從代碼也可以看到,沒什麼難度,這個函數的最難點在於知道detach()在執行過程中都幹了什麼!再重申一遍:一方面刪除fragment的View視圖;一方面將fragment從Activity的ADD隊列中移除!說是Activity的ADD隊列,倒不如說是container的ADD隊列更貼切些;因爲一個Activity上面可以有多個Container來盛裝Fragment實例組,每一個Container都會被分配一個ADD隊列來記錄當前通過add()方法,添加到這個container裏的所有fragment實例。
2、點擊「frag3 attach」按鈕的代碼

[java]  view plain  copy
  1. FragmentManager manager = getSupportFragmentManager();  
  2. Fragment fragment = manager.findFragmentByTag("fragment3");  
  3. FragmentTransaction transaction = manager.beginTransaction();  
  4. transaction.attach(fragment);  
  5. transaction.addToBackStack("attach fragment3");  
  6. transaction.commit();  
依然,相比以前的fragment操作也只多了一個transaction.attach(fragment);沒什麼難度。關鍵仍然在於知道attach()操作都做了哪些事!再次重申:一方面重建fragment的View,注意是重建!另一方面,將fragment實例添加進container的ADD隊列中;關於"frag2 detach"和"frag2 attach"的代碼就不再貼出來了,跟frag3的一樣。
好了,到這裏,有關Fragment的操作都已經講完了,下面就講講有關在Fragment操作中Android的BUG!
源碼在文章底部給出

三、系統BUG——add()和replace()千萬不要共用!!!

先寫個例子來看一下問題:
這個例子分爲兩部分,


  • 第一部分:先利用add()函數,依次add進去fragment1,fragment2,fragment3,fragment4,fragment5,然後利用"print back stack"打印出當前在回退棧中每次操作的名稱;每回退一次打一次回退棧內容,可見一切都是正常的,即回退棧頂的項,正是當前VIEW頂部顯示的內容。
  • 第二部分,如果我們先利用add()函數,依次add進去fragment1,fragment2,fragment3,fragment4,然後再利用replace函數添加進去fragment5;然後利用"print back stack"打印出當前在回退棧中每次操作的名稱;可以看到,當回退棧頂是"add fragment4"時,fragment4卻沒有出現,點擊返回按鈕,卻把這個"add fragment4"的Transaction操作給返回了。同樣的現象也發生在fragment2中;

還是先看看實現代碼:
1、添加fragment,比如添加fragment1,其它fragment2,fragment3,fragment4同理

[java]  view plain  copy
  1. Fragment1 fragment1 = new Fragment1();  
  2. addFragment(fragment1, "add fragment1");  
其中:
[java]  view plain  copy
  1. private void addFragment(Fragment fragment, String tag) {  
  2.     FragmentManager manager = getSupportFragmentManager();  
  3.     FragmentTransaction transaction = manager.beginTransaction();  
  4.     transaction.add(R.id.fragment_container, fragment);  
  5.     transaction.addToBackStack(tag);  
  6.     transaction.commit();  
  7. }  
2、replace fragment5:
代碼上沒什麼難度,在添加到回退棧時,添加TAG:"replace fragment5"
[java]  view plain  copy
  1. Fragment5 fragment5 = new Fragment5();  
  2. FragmentManager manager = getSupportFragmentManager();  
  3. FragmentTransaction transaction = manager.beginTransaction();  
  4. transaction.replace(R.id.fragment_container, fragment5);  
  5. transaction.addToBackStack("replace fragment5");  
  6. transaction.commit();  
3、打印出回退棧中的內容:
這裏要講一個函數了:
[java]  view plain  copy
  1. public int getBackStackEntryCount();//獲取回退棧中,Transaction回退操作的數量  
  2. public BackStackEntry getBackStackEntryAt(int index);//根據索引得到回退棧變量  
其中getBackStackEntryAt()返回的變量BackStackEntry,就是回退棧中保存每次transaction操作的變量;它有很多方法,其中BackStackEntry::getName()是獲取Transacion操作的名字,即通過transaction.addToBackStack("replace fragment5");傳進去的字符串。關於BackStackEntry的其它方法,靠大家自己去發掘啦,這個函數用的不多,就不再細講了。
[java]  view plain  copy
  1. TextView tv = (TextView) findViewById(R.id.tv_stack_val);  
  2.   
  3. FragmentManager manager = getSupportFragmentManager();  
  4. int count = manager.getBackStackEntryCount();  
  5. StringBuilder builder = new StringBuilder("回退棧內容爲:\n");  
  6. for (int i = --count;i>=0;i--){  
  7.     FragmentManager.BackStackEntry entry= manager.getBackStackEntryAt(i);  
  8.     builder.append(entry.getName()+"\n");  
  9. }  
  10. tv.setText(builder.toString());  
好啦,代碼看完了,要講問題了。那問題來了,爲什麼在回退棧中有add fragment4和add fragment2的操作,卻不顯示呢?
問題出在了replace()操作上,replace()操作原意的實現應該是清空container中所有的fragment實例,然後再將指定的fragment添加到container的ADD隊列中;但在清空時,他們的代碼是這樣寫的:
[java]  view plain  copy
  1. for (int i=0;i<mManager.mAdded.size(); i++) {  
  2.     Fragment old = mManager.mAdded.get(i);  
  3.     ……  
  4.     mManager.removeFragment(old, mTransition, mTransitionStyle);  
  5. }  
其中:mAdded就是我們前面說的container的ADD隊列;看他的操作:
首先,先逐個得到mAdded隊列中的fragment,即:
[java]  view plain  copy
  1. Fragment old = mManager.mAdded.get(i);  
然後,將這個fragment實例移除:
[java]  view plain  copy
  1. mManager.removeFragment(old, mTransition, mTransitionStyle);  
有沒有看出什麼問題?他把這個fragment從mAdded隊列中直接移除了!!!!那這不打亂了原來的順序了麼,在移除下一個fragment時就根本對不上號了。看不懂?沒關係,我們舉個例子來講:
比如,我們上面的,在mAdded隊列中有1,2,3,4,5這五個fragment;
首先,當i=0時,移除1,這沒錯!但它是將mAdded隊列中的1直接移除的哦!所以移除1以後,mAdded隊列的值變成了2,3,4,5
這時候,當i=1時,刪除的是3!!!!知道問題所在了吧!所以在刪除3後,mAdded隊列的值爲2,4,5
所以當i=2時,刪除的是5!!!!所以這就造成了爲什麼我們的fragment2和fragment4明明在回退棧中,即顯示不出來的原因,因爲他們在刪除時根本就沒有刪除,而在回退棧回退時卻又要跟着操作順序來回退,即remove fragment5,逐個add進去fragment4,fragment3,fragment2,fragment1,而又由於fragment4和fragment2沒有被刪除,所以出現了錯誤,導致系統哪裏出了問題,所以顯示不出來,至於是哪裏出了問題,我也不知道,因爲我嘗試了show()和attach() fragment4都還是沒有效果。可能是系統底層的問題吧。所以,這裏忠告大家,add()和replace()不能共用!!!!!