1. 程式人生 > >Pro Android學習筆記(三九):Fragment(4):基礎小例子-續

Pro Android學習筆記(三九):Fragment(4):基礎小例子-續

Step 3:實現簡介顯示類DetailFragment

在Activity的佈局xml中,對DetailFragment並沒有指定class屬性,故在setContentView()中不會自動呼叫該類,而是通過編寫showDetail(int index)來呼叫,該函式的具體編碼以後在描述,我們先來看看DetailFragment類的實現。

建立例項

在TitleFragment中,通過在xml中指定class屬性來呼叫fragment。而對於右邊部分,則是定義了FrameLayout容器,因此需要通過程式碼來建立例項。下面是通用的建議程式碼,通過靜態函式來建立例項,並設定引數。當fragment被儲存並重構時,系統回撥用預設的建構函式,並接著重新獲取初始化引數。

public class DetailFragment extends Fragment{ 
    //以靜態函式的方式,通過指定書名序號,來獲取例項,並將書名序號通過setArguments()方式設為給fragment的引數  
    public static DetailFragment newInstance (int index)

        //1、建立fragment例項
        DetailFragment df = new DetailFragment();
       //2、設定fragment的引數,在fragment中可以通過getArguments()來獲取

        Bundle args = new Bundle();
        args.putInt("index", index);
        df.setArguments(args); 
        return df;
    }
    //通過bundle方式攜帶書名序號,來獲取例項
    public static DetailFragment newInstance (Bundle bundle)
        int index = bundle.getInt("index");
        return newInstance(index);
    } 
    … 略 …
}

可以通過setArguments()來設定引數,對於fragment的生命週期而言,必須在fragment完成與activity關聯之前設定引數。即必須在onAttach(之前進行,即在構造階段或者onInflate。不是每個fragment都有onInflate狀態,例如DetailFragment,不是通過xml的<fragment>進行構造,故沒有onInflate狀態。

編寫所需的生命週期程式碼

實際上並不需要對每個生命週期都進行程式碼編寫。由於在xml中沒有指定class,不是activity中通過setContentView()來呼叫,因此沒有經歷onInflate()狀態,而是從onAttach()開始。我們需要的是為fragment建立view佈局,並具體填入資訊,可以在onCreateView()和onActivityCreated()中實現。

public class DetailFragment extends Fragment{
    private int mIndex = 0;
    
   ... 略 …

    @Override //在生命週期的早期onCreate(),獲取引數(書名序號)
    public void onCreate(Bundle savedInstanceState)
        super.onCreate(savedInstanceState);
        mIndex = getArguments().getInt("index",0); //在建立例項過程中將index設定為引數,可以在任何生命週期中獲取,此處只為了使用方面進行提取。為何我們不直接newInstance()中將mIndex=index,而是設定成為引數,我們需要考慮到fragment recreate的情況,例如螢幕轉向。此時,系統將呼叫預設的建構函式,不會執行newInstance()程式碼,但在onCreate()我們仍可從引數中獲取index。
    }

    public int getShowIndex(){
        return mIndex;
    }
 
         
    @Override //在onCreateView中建立view層級,並返回。LayoutInflater可用於inflate佈局。如果ViewGroup非空,則可以通過infalte()來獲取佈局,如果為null,說明沒有viewGroup容器可以關聯,在此所設定的,不會在UI中真正顯示。
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
        if(container == null)
            return null;

       //1、根據xml的佈局檔案建立view層級並將之返回。注意:第3個引數為false,不要將view和viewGroup進行關聯,系統會自動進行,否則會出現異常。 
        View v = inflater.inflate(R.layout.details, container,false);
       //2、設定層級中的view
        TextView tv = (TextView)v.findViewById(R.id.text1);
        tv.setText(BooksInfo.DIALOGUE[mIndex]);
        return v; 
    }
}

在onCreateView中,返回的是View hierarchy,這和引數ViewGroup contianer是什麼關係。其實這兩者都是容器。一個表示fragment所佔的空間的parent容器,就是XML檔案中的FrameLayout;一個表示填充這個空間的child容器,圖示如下:


這個用於填充FrameLayout的子容器佈局很簡單,下面為details.xml檔案。注意,我們並不需要在程式碼中將子容器放入FrameLayout中,系統會自動完成,如果我們將兩者關聯,反而會出現異常。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout …. >
    <ScrollView android:id="@+id/scroller" android:layout_width… >
        <TextView android:id="@+id/text1"   android:layout_width… />
    </ScrollView>
</LinearLayout>

Step 4:實現showDetail(int index),如何管理fragment

fragment的切換

右邊的fragment在使用者點選不同書目時進行切換。一個fragment必須在某個view容器中,即onCreateView中的ViewGroup。在XML中,我們設定了FrameLayout作為fragment的容器,因為FrameLayout比其他的簡單。在容器中可以進行fragment的切換。通過FragmentTranscation可以替換當前UI。FragmentTransaction是Fragment操作的API介面集合。相關程式碼如下:

public class FragmentBasicTest extends Activity{
    ... 略 …      
    public void showDetails(int index){ 
       //【1】、獲取當前與FrameLayout相關的fragment的物件。對fragment進行處理需要通過fragment管理器,FragmentManager可以通過 Activity的getFragmentManager()和Fragment的getFragmentManager()來獲取,通過管理器,可以獲得fragement transaction,根據id,tag獲取fragment。
        DetailFragment detail = (DetailFragment)getFragmentManager().findFragmentById(R.id.details); 
        //【2】、如果fragment物件不存在(第一次建立)或者書目變更,將進行切換

        if(detail == null || detail.getShowIndex() != index ){ 
            // 2.1)建立先的fragemnt物件
            detail = DetailFragment.newInstance(index); 
            // 2.2)beginTransaction()有點詞不達意,並不是說要獲取第一個Transaction,Transaction沒有分第幾個,它是進行Fragment操作的API介面集合。beginTransaction()是說明要通過Fragment管理器對fragment進行編輯,編輯完後通過commit()進行確認。 
            FragmentTransaction ft = getFragmentManager().beginTransaction(); 
            // 編輯的內容是replace():在FrameLayout中替換現有的UI。具體為replace(int containerId, Fragment fragment<, String tag>); tag是fragment的可選屬性,不設定即為null。
            ft.replace(R.id.details, detail); 
            ft.commit();
 
        } 
    }

}

到此,我們已經完成了對fragment的基礎小例子。

回退堆疊back stack

我們點選返回鍵,將退出activity。但是有時我們會希望,右邊的fragment能退回到上一次點選的書目簡介。在編寫相關程式碼之前,我們跟蹤一下小例子目前右邊fragment的生命週期。

利用FragmentTrasaction,將新的fragment物件加入back stack中,當用戶按回退鍵時,從back stack中pop出fragment物件,實現回退效果。back stack由fragment管理器管理,程式碼如下:

    public void showDetails(int index){ 
        DetailFragment detail = (DetailFragment)getFragmentManager().findFragmentById(R.id.details); 
        if(detail == null){ 
            addFirstFragment(index); 
        }else if(detail.getShowIndex() != index ){ 
            addFragmentToStack(index); 
        } 
    }
   
    private void addFirstFragment(int index){
        DetailFragment detail = DetailFragment.newInstance(index);
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.replace(R.id.details, detail);
        ft.commit();   
    }
    
    private void addFragmentToStack(int index){
        DetailFragment detail = DetailFragment.newInstance(index);
        FragmentTransaction ft = getFragmentManager().beginTransaction(); 
        ft.replace(R.id.details, detail);
        ft.addToBackStack(null);  //加入回退堆疊,將原有(準備被替換)的fragment加入堆疊。經過跟蹤,貌似也將新的fragment進行儲存,但是並不會進行回退顯示,回退時進行Destroy,除非下一次,作為原有fragment加入堆疊。(這裡的說法有些羅嗦和含混,如果我們考慮每次到加入回退堆疊可以處理,但是如果某次加入,某次不加入,跟蹤狀態,就很清楚)
        ft.commit();
    }

我們同樣跟蹤一下現在小例子的執行情況。

如果我們不區分是否第一次,均使用addFragmentToStack,即在第一次,也執行ft.addToBackStack(null)。將原有的(準備被替換)的fragment加入堆疊,即將null加入堆疊,在回退時,最後增加回退一步,回退到一個空白的fragment,非吾等所期。

按返回鍵,fragment回退,仍然在同一個activity中,直至回退堆疊為空。