1. 程式人生 > >Android官方文件—APP元件(Activities)(Fragments)

Android官方文件—APP元件(Activities)(Fragments)

Fragments(片段)

片段表示Activity中的一部分行為或使用者介面。您可以在單個activity中組合多個片段以構建多窗格UI,也可以在多個activity中重用片段。您可以將片段視為activity的模組化部分,它具有自己的生命週期,接收自己的輸入事件,並且可以在activity執行時新增或刪除(有點像“子activity”,您可以在不同activity中重複使用)。

片段必須始終嵌入到activity中,片段的生命週期直接受主機activity生命週期的影響。例如,當activity暫停時,其中的所有片段也都會暫停,當activity被銷燬時,所有片段也都會被銷燬。但是,當一個activity正在執行時(它處於resumed

的生命週期狀態),您可以獨立操作每個碎片,例如新增或刪除它們。執行此類片段事務時,還可以將其新增到由activity管理的後臺堆疊中 - 當發生的片段事務,activity中的後臺堆疊都會有所記錄。後向堆疊允許使用者通過按“返回”按鈕來回退片段事務(向後導航)。

將片段新增為activity佈局的一部分時,它將位於activity檢視層次結構內的ViewGroup中,並且片段需要定義自己的檢視佈局。 您可以通過在activity的佈局檔案中宣告<fragment>元素來將片段插入到您的activity佈局中。或通過在程式碼中將其新增到現有ViewGroup中。但是,片段不一定是activity佈局的一部分;您也可以使用沒有自己的UI的片段作為activity的隱形工作者。

本文件描述瞭如何構建應用程式以使用片段,包括片段在新增到activity的後臺堆疊時如何維持其狀態,與activity和activity中的其他片段共享事件,擴充套件activity的操作欄等等。

設計理念 

Android在Android 3.0(API級別11)中引入了片段,主要是為了在大螢幕(如平板電腦)上支援更加動態和靈活的UI設計。由於平板電腦的螢幕比手機的螢幕大得多,因此組合和交換UI元件的空間更大。片段允許此類設計,而無需您管理對檢視層次結構的複雜更改。通過將activity的佈局劃分為片段,您可以在執行時修改activity的外觀,並將這些更改保留在由activity管理的後臺堆疊中。

例如,新聞應用程式可以使用一個片段顯示左側文章列表,另一個片段顯示右側文章 - 兩個片段並排顯示在一個activity中,每個片段都有自己的生命週期集回撥方法並處理自己的使用者輸入事件。因此,使用者可以選擇一篇文章並在同一activity中全部閱讀,而不是使用一個activity來選擇文章和另一個activity來閱讀文章,如圖1中的平板電腦佈局所示。

您應該將每個片段設計為模組化和可重用的activity元件。也就是說,因為每個片段在自己的生命週期回撥中定義自己的佈局和自己的行為,所以可以在多個activity中重用一個片段,因此您應該設計成重用的並避免在一個片段直接操作另一個片段。這一點尤其重要,因為模組化片段允許根據不同螢幕大小來組合不同片段。在設計支援平板電腦和手機的應用程式時,您可以在不同的佈局配置中重複使用片段,以根據可用的螢幕空間優化使用者體驗。例如,在手機上,當多個片段不能放入同一activity時,可能需要將這些片段分開以單獨提供UI。

圖1.對於平板通過片段定義的兩個UI模組組合在同一個activity,但是對於手機分開設計的示例。 

例如,繼續使用新聞應用程式示例 - 當在平板電腦大小的裝置上執行時,應用程式可以在activity A中嵌入兩個片段。然而,在手機大小的螢幕上,沒有足夠的空間放置兩個片段,因此activity A僅包括文章列表的片段,當用戶選擇文章時,它啟動activity B,其中包括要閱讀這篇文章的第二個片段。因此,該應用程式通過重複使用不同組合的片段來支援平板電腦和手機,如圖1所示。

有關使用不同螢幕配置的不同片段組合設計應用程式的更多資訊,請參閱支援平板電腦和手機指南。

建立片段

圖2.片段的生命週期(當它的Activity正在執行時)。

要建立片段,您必須建立Fragment的子類(或其現有的子類)。Fragment類的程式碼看起來很

像Activity。它包含類似於activity 的回撥方法,例如onCreate(),onStart(),onPause()和onStop()。實際上,如果您要將現有的Android應用程式轉換為使用片段,則可以簡單地將程式碼從您的activity的回撥方法移動到片段的相應回撥方法中。

通常,您應該至少實現以下生命週期方法:

系統在建立片段時呼叫此方法。在這個實現中,您應該初始化在片段暫停或停止時需要要保留的的基本資訊。

當片段第一次繪製其使用者介面時,系統會呼叫此方法。要為片段繪製UI,必須從此方法返回檢視,該檢視是片段根佈局。如果片段不提供UI,則可以返回null。

系統將此方法稱為使用者離開片段的第一個回撥(儘管並不總是意味著片段被銷燬)。這通常是您應該提交儲存使用者操作狀態的地方(因為使用者可能不會回來)。

大多數應用程式應該為每個片段至少實現這三種方法,但是還應該使用其他幾種回撥方法來處理片段生命週期的各個階段。有關處理片段生命週期的部分中將更詳細地討論所有生命週期回撥方法。

您可能還想要擴充套件一些子類,而不是基本Fragment類:

顯示浮動對話方塊。使用此類建立對話方塊比使用對話方塊助手建立方法更好,因為您可以將片段對話方塊合併到由活動管理的片段的後臺堆疊中,從而允許使用者返回到被隱藏的片段中。

顯示由介面卡管理的item列表(例如SimpleCursorAdapter),類似於ListActivity。它提供了幾種管理列表檢視的方法,例如onListItemClick()回撥來處理點選事件。

將Preference物件的層次結構顯示為列表,類似於PreferenceActivity。在為應用程式建立“設定”activity 時,這非常有用。

新增使用者介面

片段通常用作activity 使用者介面的一部分,並在activity 提供自己的佈局。

要為片段提供佈局,必須實現onCreateView()回撥方法,當片段繪製其佈局時,Android系統會呼叫該方法。您對此方法的實現必須返回一個View,它是片段根佈局。

注意:如果您的片段是ListFragment的子類,則預設實現從onCreateView()返回ListView,因此您不需要實現它。

要從onCreateView()返回佈局,可以從XML中定義的佈局資源中對其進行充氣。為了幫助您這樣做,onCreateView()提供了一個LayoutInflater物件。

例如,這是Fragment的子類,它從example_fragment.xml檔案載入佈局:

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

傳遞給onCreateView()的container引數是將插入片段的父ViewGroup(來自activity 的佈局)。如果片段正在恢復,savedInstanceState引數是一個Bundle,它提供有關片段的前一個例項的資料(恢復狀態將在有關處理片段生命週期的部分中進行更多討論)。

inflate()方法有三個引數:

  • 要擴充的佈局的資源ID。
  • ViewGroup是膨脹佈局的父級。傳遞container非常重要,以便系統將佈局引數應用於所要膨脹佈局的根檢視,該佈局由其所在的父檢視指定。
  • 一個布林值,指示在充氣期間是否應將膨脹的佈局附加到ViewGroup(第二個引數)。(在這種情況下,應該傳遞false,因為系統已經將膨脹的佈局插入到容器中 - 傳遞true將在最終佈局中建立冗餘檢視組)。

現在您已經瞭解瞭如何建立提供佈局的片段。接下來,您需要將片段新增到您的activity中。

將片段新增到activity

通常,片段為宿主activity提供UI的一部分,作為宿主activity的一部分檢視嵌入。您可以通過兩種方式將片段新增到activity佈局:

  • 在activity的佈局檔案中宣告片段。

在這種情況下,您可以為片段指定佈局屬性,就像它是檢視一樣。例如,這是包含兩個片段的activity的佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

< fragment>中的android:name屬性指定要在佈局中例項化的Fragment類。

當系統建立此活動佈局時,它會例項化佈局中指定的每個片段,併為每個片段呼叫onCreateView()方法,以檢索每個片段的佈局。系統直接將片段所返回的檢視替換掉<fragment>元素。

注意:每個片段都需要一個唯一的識別符號,如果重新啟動activity,系統可以使用該識別符號來恢復片段(您可以使用它來獲取片段以執行事務,例如刪除它)。有三種方法可以為片段提供ID:

  • 提供具有唯一ID的android:id屬性。
  • 為android:tag屬性提供唯一的字串。
  • 如果您不提供前兩個,系統將使用container view的ID。
  • 或者,以程式碼的方式將片段新增到現有ViewGroup。

在activity執行的任何時候,您都可以將片段新增到activity佈局中。您只需指定一個ViewGroup來放置片段。

要在activity中進行片段事務(例如新增,刪除或替換片段),必須使用FragmentTransaction中的API。您可以從Activity中獲取FragmentTransaction的例項,如下所示:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

然後,您可以使用add()方法新增片段,指定要新增的片段以及要插入的檢視。例如:

ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();

傳遞給add()的第一個引數是應放置片段的V​​iewGroup,由資源ID指定,第二個引數是要新增的片段。 

使用FragmentTransaction進行更改後,必須呼叫commit()才能使更改生效。

新增沒有UI的片段

上面的示例顯示瞭如何向activity新增片段以提供UI。但是,您也可以使用片段為activity提供後臺行為,而無需顯示其他UI。

要新增沒有UI的片段,請使用add(Fragment,String)從activity中新增片段(為片段提供唯一的字串“tag”,而不是檢視ID)。這會新增片段,但由於它與activity佈局中的檢視無關,因此它不會接收對onCreateView()的呼叫。所以你不需要實現那個方法。

Supplying a string tag for the fragment isn't strictly for non-UI fragments—you can also supply string tags to fragments that do have a UI,但如果片段沒有UI,則字串標記是唯一的方法識別它。如果您想稍後要從activity中獲取片段,則需要使用findFragmentByTag()。

對於使用片段作為後臺工作者而沒有UI的示例activity,請參閱FragmentRetainInstance.java示例,該示例包含在SDK示例中(可通過Android SDK Manager獲得)並位於您的系統上<sdk_root> / /APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java

管理片段

要管理activity中的片段,需要使用FragmentManager。要獲取它,請從您的activity中呼叫getFragmentManager()。 您可以使用FragmentManager執行的一些操作包括:

  • 使用findFragmentById()(對於在activity佈局中提供UI的片段)或findFragmentByTag()(對於提供或不提供UI的片段),獲取activity中存在的片段。
  • 使用popBackStack()(使用者模擬Back命令)從後端堆疊彈出片段。
  • 使用addOnBackStackChangedListener()為後備棧註冊一個監聽器以進行更改。

有關這些方法和其他方法的更多資訊,請參閱FragmentManager類文件

如上一節所示,您還可以使用FragmentManager開啟FragmentTransaction,它允許您執行事務,例如新增和刪除片段。

執行片段事務

在您的activity中使用片段的一個很棒的功能是能夠新增,刪除,替換和執行其他操作,以響應使用者互動。您為activity提交的每組更改都稱為事務,您可以使用FragmentTransaction中的API執行一項更改。您還可以將每個事務儲存到由activity管理的後臺堆疊中,允許使用者向後導航片段更改(類似於向後導航activity)。

您可以像這樣從FragmentManager獲取FragmentTransaction的例項:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

每個事務都是您希望同時執行的一組更改。您可以使用add(),remove()和replace()等方法設定要為給定事務執行的所有更改。然後,要將事務應用於activity,必須呼叫commit()。

但是,在呼叫commit()之前,您可能希望呼叫addToBackStack(),以便將事務新增到片段事務的後臺堆疊中。此後備堆疊由activity管理,並允許使用者通過按“返回”按鈕返回到先前的片段狀態。

例如,以下是如何將一個片段替換為另一個片段,並保留後棧中的先前狀態:

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

在此示例中,newFragment替換當前在R.id.fragment_container ID標識的佈局容器中的任何片段(如果有)。通過呼叫addToBackStack(),替換事務將儲存到後臺,因此使用者可以通過按“返回”按鈕來反轉事務並恢復上一個片段。

如果向事務新增多個更改(例如另一個add()或remove())並呼叫addToBackStack(),則在呼叫commit()之前應用的所有更改都將作為單個事務和Back按鈕新增到後臺堆疊中將它們全部扭轉。

將更改新增到FragmentTransaction的順序無關緊要,但是:

  • 你必須最後呼叫commit()。
  • 如果要將多個片段新增到同一容器中,則新增它們的順序將決定它們在檢視層次結構中的顯示順序。

如果在執行刪除片段的事務時未呼叫addToBackStack(),則在提交事務並且使用者無法導航回到該片段時,該片段將被銷燬。然而,如果在刪除片段時呼叫addToBackStack(),則片段將停止,並且如果使用者導航回覆則將恢復。

提示:對於每個片段事務,您可以通過在提交之前呼叫setTransition()來應用過渡動畫。

呼叫commit()不會立即執行事務。相反,它會盡快的在activity的UI執行緒(“主”執行緒)上執行。但是,如果需要,可以從UI執行緒呼叫executePendingTransactions()以立即執行commit()提交事務。除非事務依賴於其他執行緒,否則通常不需要這樣做。

警告:您只能在activity儲存其狀態(使用者離開activity時)之前使用commit()提交事務。如果在該點之後嘗試提交,則將引發異常。這是因為如果需要恢復activity,則提交後的狀態可能會丟失。對於可以丟失提交的情況,請使用commitAllowingStateLoss()。

與Activity通訊

儘管Fragment是作為一個獨立於Activity的物件實現的,並且可以在多個activity中使用,但是片段的某個例項直接與包含它的activity相關聯。

具體來說,片段可以使用getActivity()訪問Activity例項,並輕鬆執行任務,例如在activity佈局中查詢檢視:

View listView = getActivity().findViewById(R.id.list);

同樣,您的activity可以通過使用findFragmentById()或findFragmentByTag()從FragmentManager獲取對Fragment的引用來呼叫片段中的方法。例如:

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

建立Activity的事件回撥

在某些情況下,您可能需要一個片段來與活動共享事件。一種好方法是在片段內定義一個回撥介面,並要求宿主Activity實現它。當Activity通過介面收到回撥時,它可以根據需要與佈局中的其他片段共享資訊。

例如,如果新聞應用程式在Activity中有兩個片段 - 一個用於顯示文章列表(片段A)而另一個用於顯示文章(片段B) - 那麼片段A必須在選擇列表項時告知Activity讓它可以告訴片段B顯示文章。在這種情況下,OnArticleSelectedListener介面在片段A中宣告:

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

然後,宿主Activity實現OnArticleSelectedListener介面並覆蓋onArticleSelected()以從片段A通知片段B事件。為確保宿主Activity實現此介面,片段A的onAttach()回撥方法(系統呼叫時)將片段新增到活動中)通過強制轉換傳遞給onAttach()的Activity來例項化OnArticleSelectedListener的例項:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
    ...
}

如果Activity尚未實現介面,則片段將丟擲ClassCastException。如果Activity尚實現了介面,則mListener成員儲存對活動的OnArticleSelectedListener實現的引用,以便片段A可以通過呼叫OnArticleSelectedListener介面定義的方法與活動共享事件。例如,如果片段A是ListFragment的擴充套件,則每次使用者單擊列表項時,系統都會呼叫片段中的onListItemClick(),然後呼叫onArticleSelected()以與活動共享事件:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);
    }
    ...
}

傳遞給onListItemClick()的id引數是單擊項的行ID,Activity(或其他片段)用於從應用程式的content  provider中獲取文章。

Content Providers文件中提供了有關使用content provider的更多資訊。

將Item新增到App Bar

您的片段可以通過實現onCreateOptionsMenu()將選單項提供給Activity的“選項”選單(以及App Bar)。但是,為了使此方法被呼叫,您必須在onCreate()期間呼叫setHasOptionsMenu(),以指示該片段要將項新增到選項選單(否則,片段將不會接收對onCreateOptionsMenu()的呼叫)。

然後,您從片段新增到“選項選單”的任何專案都將附加到現有選單項。當選擇選單項時,片段還接收對onOptionsItemSelected()的回撥。

您還可以在片段佈局中註冊檢視,以通過呼叫registerForContextMenu()來提供上下文選單。當用戶開啟上下文選單時,片段將接收對onCreateContextMenu()的呼叫。當用戶選擇一個專案時,該片段接收對onContextItemSelected()的呼叫。

注意:雖然您的片段會為其新增的每個選單項收到專案選擇的回撥,但當用戶選擇選單項時,活動首先接收相應的回撥。如果活動的on-item-selected回撥的實現不消費事件,則事件將傳遞給片段的回撥。選項選單和上下文選單也是如此。

有關選單的更多資訊,請參閱選單開發人員指南和App Bar練習類。

處理片段生命週期

圖3.Activity生命週期對片段生命週期的影響。

管理片段的生命週期很像管理Activity的生命週期。如Activity一樣,片段可以以三種狀態存在:

Resumed

片段在執行Activity中可見。

Paused

另一個Activity位於前景並具有焦點,但此片段所在的Activity仍然可見(前景Activity部分透明或未覆蓋整個螢幕)。

Stopped

片段不可見。宿主Activity已停止或片段已從Activity中刪除但已新增到後臺堆疊。停止的片段仍然存在(系統保留所有狀態和成員資訊)。但是,它不再對使用者可見,並且如果Activity被殺死片段也將被殺死。

也像Activity一樣,您可以使用Bundle保留片段的狀態,以防Activity的程序被終止時您需要在重新建立活動時恢復片段狀態。您可以在片段的onSaveInstanceState()回撥期間儲存狀態,並在onCreate(),onCreateView()或onActivityCreated()期間恢復它。有關儲存狀態的更多資訊,請參閱“Activities”文件。

Activity和片段之間生命週期中最顯著的差異是如何將其儲存在其各自的後臺堆疊中。預設情況下,當Activity stopped時被置於由系統管理的活動的後堆疊中(以便使用者可以使用“後退”按鈕導航回到它,如任務和後臺堆疊中所述)。然而,只有當您在刪除片段的事務期間通過呼叫addToBackStack()顯式請求儲存例項時,才會將片段放入由宿主Activity 管理的後臺堆疊中。

因此,管理片段生命週期與管理Activity生命週期非常相似。因此,管理Activity 生命週期的方案也適用於片段。但是,您還需要了解的是,Activity 的生命週期如何影響片段的生命週期。

警告:如果在Fragment中需要Context物件,則可以呼叫getActivity()。但是,只有在片段附加到活動時才要小心呼叫getActivity()。當片段尚未附加,或在其生命週期結束時被分離時,getActivity()將返回null。

與Activity生命週期協調

片段所在Activity的生命週期直接影響片段的生命週期,因此Activity的每個生命週期回撥都會導致每個片段的類似回撥。例如,當Activity收到onPause()時,Activity中的每個片段都會收到onPause()。

但是,片段還有一些額外的生命週期回撥,用於處理與Activity的唯一互動,以便執行建立和銷燬片段UI等操作。這些額外的回撥方法是:

當片段與Activity相關聯時呼叫(此處傳遞Activity)。

呼叫以建立與片段關聯的檢視層次結構。

在活動的onCreate()方法返回時呼叫。

在銷燬與片段關聯的檢視層次結構時呼叫。

當片段與活動分離時呼叫。

圖3說明了片段生命週期的流程,因為它受到宿主Activity的影響。在此圖中,您可以看到Activity的每個狀態變化時如何確定片段可以接收哪些回撥方法。例如,當Activity收到onCreate()回撥時,Activity中的片段只接收onActivityCreated()回撥。

一旦Activity達到resume狀態,您就可以自由地向Activity新增和刪除片段。因此,只有當活動處於resume狀態時,片段的生命週期才能獨立地改變。

但是,當Activity離開resume狀態時,該片段將再次依賴於Activity影響其生命週期。

示例

為了將本文件中討論的所有內容放在一起,下面是使用兩個片段建立雙窗格佈局的活動示例。下面的活動包括一個片段,用於顯示莎士比亞戲劇標題列表,另一個用於顯示從列表中選擇的播放摘要。它還演示瞭如何根據螢幕配置提供不同的片段配置。

注意:FragmentLayout.java中提供了此Activity的完整原始碼

在onCreate()中,main activity以通常的方式建立佈局:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.fragment_layout);
}

應用的佈局是fragment_layout.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent"
            android:background="?android:attr/detailsElementBackground" />

</LinearLayout>

使用此佈局,系統會在activity載入佈局時例項化TitlesFragment(列出播放標題),而FrameLayout(用於顯示播放摘要的片段將在其中)會佔據螢幕右側的空間,但起初仍然是空的。正如您將在下面看到的那樣,直到使用者從列表中選擇一個片段放入FrameLayout中後,才填充內容。

但是,並非所有螢幕配置都足夠寬,以便並排顯示播放列表和摘要。因此,上面的佈局僅用於橫向螢幕配置,方法是將其儲存在res / layout-land / fragment_layout.xml中。

因此,當螢幕處於縱向時,系統將應用以下佈局,該佈局儲存在res / layout / fragment_layout.xml中:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>

此佈局僅包含TitlesFragment。這意味著,當裝置處於縱向方向時,只能看到播放標題列表。因此,當用戶單擊此配置中的列表項時,應用程式將啟動新activity以顯示摘要,而不是載入第二個片段。

接下來,您可以看到如何在片段類中完成此操作。首先是TitlesFragment,它顯示了莎士比亞戲劇名單。此片段擴充套件了ListFragment並依賴它來處理大多數列表檢視工作。

在檢視此程式碼時,請注意當用戶單擊列表項時有兩種可能的行為:根據兩個佈局中的哪一個處於活動狀態,它可以建立並顯示一個新片段以在同一活動中的顯示詳細資訊(新增片段到FrameLayout),或者開始一個新的activity(可以顯示片段)。

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Populate list with our static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            // Restore last state for checked position.
            mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }

        if (mDualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // Make sure our UI is in the correct state.
            showDetails(mCurCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    void showDetails(int index) {
        mCurCheckPosition = index;

        if (mDualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            getListView().setItemChecked(index, true);

            // Check what fragment is currently shown, replace if needed.
            DetailsFragment details = (DetailsFragment)
                    getFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index);

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                if (index == 0) {
                    ft.replace(R.id.details, details);
                } else {
                    ft.replace(R.id.a_item, details);
                }
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}

第二個片段DetailsFragment顯示從TitlesFragment列表中選擇的專案的播放摘要:

public static class DetailsFragment extends Fragment {
    /**
     * Create a new instance of DetailsFragment, initialized to
     * show the text at 'index'.
     */
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist.  The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed.  Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}

回想一下TitlesFragment類,如果使用者單擊列表項並且當前佈局不包含R.id.details檢視(這是DetailsFragment所屬的位置),則應用程式啟動DetailsActivity活動以顯示內容這個專案。

這是DetailsActivity,它只是嵌入DetailsFragment以在螢幕處於縱向時顯示所選的播放摘要:

public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}

請注意,如果配置是橫向的,則此activity將自行結束掉,以便主activity可以接管並在TitlesFragment旁邊顯示DetailsFragment。如果使用者在縱向方向上啟動DetailsActivity,但隨後旋轉到橫向(重新啟動當前活動),則會發生這種情況。

有關使用片段的更多示例(以及此示例的完整原始檔),請參閱ApiDemos中提供的API演示示例應用程式(可從Samples SDK元件下載)。