讀古今文學網 > Android程序設計:第2版 > Fragment事務 >

Fragment事務

除了使用fragment標籤,新的代碼也說明了fragment事務。我們再次對該應用進行擴展來對這個概念進行進一步的說明。

但是,在探討事務之前,我們需要先簡單地做一些說明。在此之前我們已經指出Android開發者文檔建議fragment子類不要有顯式的構造函數。那麼,外部對像如何為新的fragment執行初始化呢?Fragment類支持兩種方法:setArguments和getArguments,這兩個方法提供了這種功能。它們允許外部調用方(可能是fragment創建者)在fragment中存儲Bundle,並且該fragment在後期可以重新獲取這個bundle。

以上說明了fragment的新實例、Bundle和類似構造函數的setArguments函數的關係。可以把它們組合成Fragment對象的靜態工廠方法,如下所示:


public static DateTime newInstance(Date time) {
    Bundle init = new Bundle;
    init.putString(
        DateTime.TAG_DATE_TIME,
        getDateTimeString(time));
    DateTime frag = new DateTime;
    frag.setArguments(init);
    return frag;
}
private static String getDateTimeString(Date time) {
    return new SimpleDateFormat(\"d MMM yyyy HH:mm:ss\")
        .format(time);
}
  

現在,我們可以使用靜態工廠方法SimpleFragment的onCreate方法來創建fragment的新實例,對其參數bundle執行正確的初始化。這段代碼和預覽版本的幾乎完全相同,區別在於它使用的是DateTime的靜態工廠方法,並給它傳遞了一個參數:


@Override
public void onCreate(Bundle state) {
    super.onCreate(state);
    setContentView(R.layout.main);
    FragmentManager fragMgr = getFragmentManager;
    FragmentTransaction xact = fragMgr.beginTransaction;
    if (null == fragMgr.findFragmentByTag(FRAG1_TAG)) {
        xact.add(
            R.id.date_time,
            DateTime.newInstance(new Date),
            FRAG1_TAG);
    }
    xact.commit;
}
  

最後,onCreate方法從傳遞的參數bundle中獲取初始化數據,除非從之前的版本中可以獲取狀態:


@Override
public void onCreate(Bundle state) {
    super.onCreate(state);
    if (null == state) { state = getArguments; }
    if (null != state) { time = state.getString(TAG_DATE_TIME); }
    if (null == time) { time = getDateTimeString(new Date); }
}
  

對這個應用的修改,到目前為止,還尚未影響到執行結果。但是,其實現是非常不同的,而且更加靈活。特別是,現在有了可以在外部初始化的fragment,可以用於說明事務。

正如其名,Fragment事務的思想在於所有的變化作為單一的、原子性的操作。為了說明這一點,我們對示例代碼做最後的擴展:添加創建一組fragment的功能。

以下是新的佈局:


<LinearLayout
    xmlns:android=\"http://schemas.android.com/apk/res/android\"
    android:orientation=\"vertical\"
    android:layout_
    android:layout_
      >
      <Button
            android:id=\"@+id/new_fragments\"
            android:layout_
            android:layout_
            android:layout_weight=\"1\"
            android:textSize=\"24dp\"
            android:text=\"@string/doit\"
            />
      <FrameLayout
            android:id=\"@+id/date_time2\"
            android:layout_
            android:layout_
            android:layout_weight=\"2\"
            android:background=\"@color/blue\"
            />
      <FrameLayout
            android:id=\"@+id/date_time\"
            android:layout_
            android:layout_
            android:layout_weight=\"2\"
            android:background=\"@color/green\"
            />
</LinearLayout>
  

以下是在SimpleFragment的onCreate方法中添加的代碼:


public void onCreate(Bundle state) {
      super.onCreate(state);
      setContentView(R.layout.main);
      ((Button) findViewById(R.id.new_fragments))
      .setOnClickListener(
            new Button.OnClickListener {
                  @Override
                  public void onClick(View v) { update; }
            });
      Date time = new Date;
      FragmentManager fragMgr = getFragmentManager;
      FragmentTransaction xact = fragMgr.beginTransaction;
      if (null == fragMgr.findFragmentByTag(FRAG1_TAG)) {
            xact.add(
                  R.id.date_time,
                  DateTime.newInstance(time),
                  FRAG1_TAG);
      }
      if (null == fragMgr.findFragmentByTag(FRAG2_TAG)) {
            xact.add(
                  R.id.date_time2,
                  DateTime.newInstance(time),
                  FRAG2_TAG);
      }
      xact.commit;
}
  

最後,該示例代碼的執行結果和之前有所不同。運行時,它看起來如圖7-2所示。

圖7-2:Fragment事務

兩個fragment都顯示完全相同的日期和時間,因為給它們傳遞的是相同的值。訪問其他應用再返回演示應用或旋轉顯示屏幕都不會導致應用丟失狀態。因此,我們可以實現一個按鈕,如例7-1所示:

例7-1:替換當前的fragment


void update {
    Date time = new Date;
    FragmentTransaction xact
      = getFragmentManager.beginTransaction;
    xact.replace(
      R.id.date_time,
      DateTime.newInstance(time),
      FRAG1_TAG);
    xact.replace(
      R.id.date_time2,
      DateTime.newInstance(time),
      FRAG2_TAG);
    xact.addToBackStack(null);
    xact.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    xact.commit;
}
  

該方法實際上利用了fragment事務的原子性。它看起來很像SimpleFragment的onCreate方法的初始化代碼。但是,它沒有使用事務來添加新的fragment實例,而是替換了當前的fragment。在方法最後調用commit導致新的fragment同時變得不可見。藍色和綠色總是同步的。

警告:在佈局中創建的fragment(使用XML fragment標籤)永遠都不能被動態創建的fragment替換。雖然從外觀上很難區分這兩種不同方式創建的fragment,但其生命週期有很大區別。並不是說不能在應用中同時使用這兩種方式創建的fragment,但是不要相互替換。例如,如果佈局的fragment被動態創建的fragment替換,則調用setContentView時會產生Bug,而且該Bug難以查找和修正。這種問題的常見現象是彈出「Fragment did not create a view」的IllegalStateException異常。

這是我們要介紹的fragment的最後一個基礎特徵,即備用棧(back stack)。如果你順序運行了幾個活動,則可以使用後退按鈕按順序返回。該行為也適用於fragment事務。

如果你運行該應用,其顯示看起來如圖7-2所示。如果你按下顯示屏最上方的按鈕,則藍色和綠色的fragment會同時更新。但是,更好的是,當你按下後退按鈕(在顯示屏的右下角向左的箭頭圖標),你會發現按下「Do It!」按鈕時,它們會以相反的順序更新。例如,如果兩個fragment都顯示「5 Apr 2011 12:49:32」,則當你按下「Do It!」按鈕時,顯示屏會執行更新,藍色和綠色區域都顯示日期時間為「5 Apr 2011 13:02:43」。當你按下後退按鈕時,兩個fragment都會顯示「5 Apr 2011 12:49:32」。整個事務(更新兩個fragment)是作為單一事件插入備用棧中的。當你按下回退按鈕時,會刪除整個事務,顯示前一個事務的整個狀態。