1. 程式人生 > >Fragment詳解之三——管理Fragment(1)

Fragment詳解之三——管理Fragment(1)

前言:follow your heart,be your own king

相關文章:


前面給大家稍微看了要怎麼使用fragment,在上篇中,我們也初步接觸到了add,replace這些fragment操作的函式,下面就再詳細講講如何管理Fragment頁面吧。

一、概述

 1、FragmentManager

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

manager.findFragmentById();  //根據ID來找到對應的Fragment例項,主要用在靜態新增fragment的佈局中,因為靜態新增的fragment才會有ID
manager.findFragmentByTag();//根據TAG找到對應的Fragment例項,主要用於在動態新增的fragment中,根據TAG來找到fragment例項
manager.getFragments();//獲取所有被ADD進Activity中的Fragment

2、FragmentTransaction

一般用來對當前的Fragment進行管理,包括add,replace,remove;
常用的針對Fragment的方法有:
//將一個fragment例項新增到Activity的最上層
add(int containerViewId, Fragment fragment, String tag);
//將一個fragment例項從Activity的fragment佇列中刪除
remove(Fragment fragment);
//替換containerViewId中的fragment例項,注意,它首先把containerViewId中所有fragment刪除,然後再add進去當前的fragment
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:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff00f0"
    android:orientation="vertical" >
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is fragment 1"
        android:textColor="#000000"
        android:textSize="25sp" />

</LinearLayout>
fragment2.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffff00"
    android:orientation="vertical" >
    
    <TextView
        android:id="@+id/fragment2_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is fragment 2"
        android:textColor="#000000"
        android:textSize="25sp" />
    
</LinearLayout>

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

Fragment1:
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) {
		return inflater.inflate(R.layout.fragment1, container, false);
	}

}
與上一篇一樣,也只是在onCreateView()時返回對應的佈局。同樣,Fragment2的定義如下:
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) {
        return inflater.inflate(R.layout.fragment2, container, false);
    }
}

3、MainActivity的佈局

從上面的的效果圖中也可以看出大概的佈局,首先是三個Button,最下方是一個FrameLayout佈局,它是用來做為container動態盛裝fragment的;它就像是一個佔位符,你設定多大,它其中的fragment就最大能有多大。記住,fragment也是Activity中的一個普通控制元件而已,只不過它可以像Activity一樣用於顯示的同時還能用來盛裝其它控制元件!作為fragment的容器,即可以用FrameLayout也可以用LinearLayout或者RelativeLayout,都是一樣的。activity_main.xml的佈局程式碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_add_frag1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ADD  Fragment1" />

    <Button
        android:id="@+id/btn_add_frag2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ADD  Fragment2" />

    <Button
        android:id="@+id/btn_remove_frag2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Remove  Fragment2" />

    <Button
        android:id="@+id/btn_repalce_frag1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="replace  Fragment1" />

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

4、MainActivity的實現:

(1)首先,先寫一個新增fragment到Activity中的函式:
private void addFragment(Fragment fragment, String tag) {
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.add(R.id.fragment_container, fragment, tag);
    transaction.commit();
}
  • 先是傳進來兩個引數,一個是要新增的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”按鈕時:

Fragment1 fragment1 = new Fragment1();
addFragment(fragment1, "fragment1");
當點選“ADD Fragment2”按鈕時:
Fragment2 fragment2 = new Fragment2();
addFragment(fragment2, "fragment2");
這裡需要注意的是,當我們新增每個Fragment例項時,都傳進去一個對應的TAG,fragment1對應的是“fragment1”,fragment2對應的是“fragment2”,通過這些TAG,我們就可以通過findFragmentByTag()來獲取它們了。
(3)RemoveFragment2:
下面就是當點選“RemoveFragment2”按鈕時的程式碼操作了:
private void removeFragment2() {
    FragmentManager manager = getSupportFragmentManager();
    Fragment fragment = manager.findFragmentByTag("fragment2");
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.remove(fragment);
    transaction.commit();
}
首先是通過
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的程式碼:

private void replaceFragment1() {
    FragmentManager manager = getSupportFragmentManager();
    Fragment2 fragment2 = new Fragment2();
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.replace(R.id.fragment_container, fragment2);
    transaction.commit();
}
這裡有個注意的問題:
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()將其新增到回退棧中。
transaction.addToBackStack(String tag);
在需要回退時,使用popBackStack()將最上層的操作彈出回退棧。
manager.popBackStack();
這裡的popBackStack()是彈出預設的最上層的棧頂內容。
當棧中有多層時,我們可以根據id或TAG標識來指定彈出到的操作所在層。函式如下:
void popBackStack(int id, int flags);
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時,表示連著引數一指定的這一層一起退出棧; 
  • 除了這幾個函式,還有下面幾個函式:有關他們的使用,我們在這小部分結尾時會提到
popBackStackImmediate()
popBackStackImmediate(String tag)
popBackStackImmediate(String tag, int flag)
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”,其實呼叫的方法是:
manager.popBackStack("fragment2",0);//方法一,通過TAG回退
從這裡可以看出,要回退到新增ADD Fragment2的狀態,注意最後一個引數,這裡設為0,表明,要回退ADD Fragment2的之後的操作,將ADD Fragment2的操作置為棧頂。從效果圖中也可以看出,點選後的檢視在Fragment2的位置
4、最後仍然是先點選"Add Fragment3"和"ADD Fragment4",將棧還原到操作1完成後的棧狀態。然後點選“BackToStack2_INCLUSIVE”;其呼叫的方法是:
manager.popBackStack("fragment2",FragmentManager.POP_BACK_STACK_INCLUSIVE);//方法一,通過TAG回退
這裡與上面的主要不同點在於第二個引數,這裡設定為POP_BACK_STACK_INCLUSIVE,即在出棧時連帶ADD Fragment2的操作一塊出棧,放在棧頂的是ADD Fragment1的操作,所以放在介面上就是顯示的是Fragment1的檢視。
下面我們看看具體實現:
(1)、首先,與上部分一樣,先新增四個Fragment,並用背景色和文字來區分。這部分程式碼我們就不講了。
主要看看點選按鈕的程式碼處理方法。
(2)、首先是新增Fragment1:
Fragment1 fragment1 = new Fragment1();
stackID1 = addFragment(fragment1,"fragment1");
其中:
private int stackID1,stackID2,stackID3,stackID4;
private int addFragment(Fragment fragment,String stackName){
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.add(R.id.fragment_container,fragment);
    transaction.addToBackStack(stackName);
    return transaction.commit();
}
首先,這裡的stackID1,stackID2,stackID3,stackID4是用來儲存每次commit()後返回的Transaction的ID值。在void popBackStack(int id, int flags);時,其中的引數id就是這個值
然後在每次ADD操作後,利用addToBackStack(string name)將每次ADD操作新增進回退棧中;
同樣,新增Fragment2的程式碼如下,新增Fragment3,Fragment4的方法同理
Fragment2 fragment2 = new Fragment2();
stackID2 = addFragment(fragment2,"fragment2");
(3)、然後是回退棧頂內容:
private void popBackStack(){
    FragmentManager manager = getSupportFragmentManager();
    manager.popBackStack();
}
(4)、接著是點選BackToFrag2_0按鈕的內容,這裡有兩種方法實現,一種是指定TAG,一種是利用Commit()返回的ID
private void popBackStackToFrag2_0(){
	FragmentManager manager = getSupportFragmentManager();
	manager.popBackStack("fragment2",0);//方法一,通過TAG回退
	// manager.popBackStack(stackID2,0);//方法二,通過Transaction ID回退
}
(5)、最後是點選BackToFrag2_INCLUSIVE按鈕的程式碼:
private void popBackStackToFrag2_Inclusive(){
    FragmentManager manager = getSupportFragmentManager();
    manager.popBackStack("fragment2",FragmentManager.POP_BACK_STACK_INCLUSIVE);//方法一,通過TAG回退
// manager.popBackStack(stackID2,FragmentManager.POP_BACK_STACK_INCLUSIVE);//方法二,通過Transaction ID回退
}
好了,到這裡,有關回滾的基本使用就結束了,需要要注意的是:
使用popBackStack()來彈出棧內容的話,呼叫該方法後會將事物操作插入到FragmentManager的操作佇列,只有當輪詢到該事物時才能執行。如果想立即執行事物的話,需要使用下面幾個對應的方法:
popBackStackImmediate()
popBackStackImmediate(String tag)
popBackStackImmediate(String tag, int flag)
popBackStackImmediate(int id, int flag)

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

FragmentManager還為我們提供了監控回退棧狀態改變的方法:
FragmentManager::addOnBackStackChangedListener(listener);//新增監聽器
FragmentManager::removeOnBackStackChangedListener(listener);//移除監聽器
通過新增監聽器,就可以在回退棧內容改變時,及時收到通知;
我們在上面程式碼的基礎上,在MainAcitivy中為FragmentManager新增一個監聽器,當回退棧狀態改變時,打出一個LOG。具體實現如下:
(1)、OnCreate()中:
為fragmentManger新增一個監聽器:
FragmentManager manager = getSupportFragmentManager();
listener = new FragmentManager.OnBackStackChangedListener() {
	
	@Override
	public void onBackStackChanged() {
		// TODO Auto-generated method stub
		Log.d("qijian","backstack changed");
	}
};
manager.addOnBackStackChangedListener(listener);
(2)、當onDestory()中將監聽器remove掉:
protected void onDestroy() {
	// TODO Auto-generated method stub
	super.onDestroy();
	FragmentManager manager = getSupportFragmentManager();
	manager.removeOnBackStackChangedListener(listener);
}

大家一定要注意,不管是這裡的回退棧的監聽還是其它的監聽器,在頁面對應的銷燬時,都要記得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的程式碼如下:

private void addFragment1() {
    Fragment1 fragment1 = new Fragment1();
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.add(R.id.fragment_container, fragment1);
    transaction.addToBackStack("fragment1");
    transaction.commit();
}
3、然後是新增其它三個Fragment的程式碼如下:
private void addOtherFragments() {
    Fragment2 fragment2 = new Fragment2();
    Fragment3 fragment3 = new Fragment3();
    Fragment4 fragment4 = new Fragment4();
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.add(R.id.fragment_container, fragment2);
    transaction.add(R.id.fragment_container, fragment3);
    transaction.add(R.id.fragment_container, fragment4);
    transaction.addToBackStack("other fragments");
    transaction.commit();
}
再一次重申,從程式碼中也可以看到,是依次將Fragment2、Fragment3、Fragment4加入容器之後,再提交一次事務,所以下面回滾時,會將他們反順序的依次刪除。即remove(fragment4)、remove(fragment3)、remove(fragment2)
4、點選popBackStack按鈕時的程式碼
private void popBackStack() {
    FragmentManager manager = getSupportFragmentManager();
    manager.popBackStack();
}
好了,到這裡這篇文章就結束了,這篇文章太TM長了……大家估計看得也快要吐了……知識點太多,又想能給大家講的淺顯易懂就只有拉長篇幅了……

原始碼中有三個工程:
1、《harvicBlog3_1》:對應第二部分:add()、replace()、remove()使用方法示例
2、《harvicBlog3_2》:對應第三部分《有關回滾——FragmentTransaction》中的:FragmentTransaction事務回滾使用方法和回退棧內容監聽部分
3、《harvicBlog3_3》:對應第三部分《有關回滾——FragmentTransaction》中的:Transaction事務回退的原則部分

如果本文有幫到你,記得加關注哦