Android開發者指南-片段(Fragment)

片段(Fragment)

一個片段代表了一個Activity的一種行爲或是其用戶界面的一個區域。可以在一個單獨的活動中組合多個分片來組建一個多面板UI,並在不同的活動中多次利用同一個分片。可以把片段理解爲一個活動的一個模塊化部分,有其自己的生命週期,接收其自己的輸入事件,並且可以在活動運行過程中添加或移除一個片段。(譯註:網絡上也有“碎片”的譯法,不過目前還並沒有統一的名稱。在這裏姑且稱爲“片段”,個人感覺比“碎片”聽起來舒服一些 :)  )

一個片段必須被嵌在一個活動中,它的生命週期與該活動的有着緊密聯繫。例如,當活動被暫停(pause),該活動中所有的片段也會暫停,當活動被銷燬(destroy),其中所有的片段也會被銷燬。不過,當活動在運行時(處於resumed生命週期狀態),可以單獨改變每一個片段,如添加或是刪除它們。當進行這樣的片段處理時,還能將片段加入一個由該活動管理的返回棧——每個活動中的返回棧條目是一段發生過的片段處理的記錄。返回棧允許用戶通過按下BACK鍵撤銷一個片段事務(反向導航)。

在添加一個片段作爲活動佈局的一部分時,它將存在於活動的視圖層級的ViewGroup中,並定義其自有的視圖佈局。可以通過在活動佈局文件中以<fragment>元素聲明片段,或是在程序代碼中添加至已有的ViewGroup)來將一個片段插入活動的佈局之中。不過,片段並不一定要是活動佈局的一部分;還可以將片段作爲一個活動的不可見部分使用。

本文檔描述瞭如何使用片段來構建應用程序,包括片段如何在被加入活動的返回棧時維護其狀態,與活動及活動內的其他片段共享事件,以及和活動的動作條相結合等。

設計理念

Android在Android 3.0(API級別“Honeycomb”蜂巢)中引入了片段,用以在平板等大屏幕上支持更爲動態而靈活的UI設計。由於平板等屏幕比手機的要大許多,因此有更多的空間來交互組合UI組件。片段可以在不必考慮視圖層級的複雜操作的情況下實現這種設計。通過把活動的佈局分成一個個片段,就可以在運行時改變活動的外觀並在該活動所管理的返回棧中保存這些變化。

例如,一個新聞程序可以在左側用一個片段來展示條目列表,在另一邊的片段中顯示一條條目——兩個片段同時顯示在一個活動中的兩邊,且都有自己的生命週期回饋方法並處理其自有的用戶輸入事件。因此,相比一個活動選擇條目另一個活動閱讀條目,現在用戶可以在同一個活動中選擇條目並閱讀,就像圖1所示的那樣。

圖1 一個演示瞭如何通過片段將兩個獨立的UI模塊併入一個活動的範例

一個片段應該是程序中的一個模塊化的可重用的組件。也就是說,因爲片段定義了其自有的佈局以及使用了自有生命週期回饋方法的自有行爲,所以可以將一個片段用在多個活動之中。這是很重要的一點,它使得能夠在不同屏幕尺寸上提供不同的用戶體驗。例如,可以只在屏幕尺寸足夠大時纔在一個活動中包含多個片段,否則,就將不同片段分在不同的活動中使用。

例如——還是那個新聞程序的例子——程序可以在運行於一個超大屏幕設備(extra large screen,比如平板電腦)時將兩個片段嵌入在一個Activity A中。不過,在普通尺寸屏幕的設備(例如,手機)上,就沒有足夠的空間放下兩個片段,所以Activity A僅包含了條目列表功能的片段,而當用戶選擇了一個條目時,它將啓動包含了閱讀條目的片段的Activity B。因此,如圖1所示程序將同時支持兩種設計模式。

創建一個片段

要創建一個片段,必須創建一個Fragment(或它的一個已有的子類)的子類。Fragment類的代碼看起來和一個Activity很相似。它包含了和一個活動類似的回饋方法,例如onCreate()、onStart()、onPause()和onStop()。事實上,如果要把一個已有的Android程序改爲通過片段來實現,只需簡單地把代碼中的活動的回饋方法改爲相應的片段的回饋方法。

通常,至少需要實現以下的生命週期方法:

onCreate()

系統在創建片段時將調用這個方法。在其實現中,應當初始化那些希望在該片段暫停或停止時被保留的必要組件以供之後繼續使用。

onCreateView()

系統在片段第一次繪製其用戶界面時將調用這個方法。要繪製片段的UI,就必須從這個方法返回一個片段佈局的根View。如果片段不提供UI,可以只返回一個null。

onPause()

系統將調用該方法作爲用戶將要離開該片段的第一個標誌(儘管這不意味着片段一定就會被銷燬)。通常應當在這裏保存當前用戶進行的操作(因爲用戶之後或許不會返回該片段了)。

大部分的程序應當爲每一個片段至少實現以上三個方法,不過還有一些其他的回饋方法可以用來處理片段生命週期的不同階段。所有的生命週期回饋方法會在之後的“處理片段生命週期”一節再討論。

圖2. 一個片段的生命週期(當其所屬的活動處於運行狀態時)

除了Fragment基類之外,還有一些可供繼承的子類:

DialogFragment

顯示一個浮動對話框。可以用該類來創建對話框而不是用Acitivity類中的對話框輔助方法來創建,這樣就可以將一個片段對話框加入由活動所管理的片段的返回棧,令用戶可以返回到一個已被捨棄的片段。

ListFragment

顯示一個由某一適配器(例如SimpleCursorAdapter)管理的項目列表,類似於ListActivity。它提供了好幾種管理列表視圖的方法,例如onListItemClick()回饋方法以處理點擊事件。

PreferenceFragment(偏好設置片段)

以列表方式顯示一個Preference對象的層級,類似於PreferenceActivity。當爲程序創建“設置”活動時這是很有用的。

添加用戶界面

一個片段通常被用於一個活動的用戶界面,其自有的佈局將成爲該活動的一部分。

要爲一個片段提供佈局,就必須實現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(來自於該活動的佈局),片段的佈局將被插入其中。savedInstanceState參數是在返回片段時提供之前片段實例數據的一個Bundle(將在“處理片段生命週期”一節中更深入地討論狀態恢復)。

創建一個佈局

在上面的範例中,R.layout.example_fragment是指向保存於程序資源中的一個名爲example_fragment.xml佈局資源的引用。關於如何在XML中創建一個佈局的更多信息,請參見“用戶界面”文檔。

inflate()方法需要三個參數:

  • 希望生成的佈局的資源ID
  • 要生成的佈局的父級ViewGroup。爲了使系統將佈局參數傳遞給生成的佈局的根視圖,就需要傳遞該container。這將由當前正在運行的父視圖來決定。
  • 一個表明了生成的佈局在生成過程中是否應該和ViewGroup(即第二個參數)相關聯的布爾值。(在這個例子裏,因爲系統已經將生成的佈局插入到了container內所以值爲false——如果是true則會在最終的佈局中產生一個冗餘的視圖組。)

現在已經瞭解瞭如何創建一個提供了佈局的片段。接下來,需要將該片段添加到活動中去。

向活動添加一個片段

通常,片段作爲宿主活動的UI的一部分嵌於該活動整體視圖層級之中。有兩種方式可以將一個片段添加到活動的佈局:

  • 在活動的佈局文件裏聲明該片段。

這種情況下,如果片段是一個視圖,可以爲其指定佈局屬性。例如,這裏有一個含有兩個片段的活動的佈局文件:

<?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>元素處直接插入由該片段返回的View。

注意:每一個片段需要在系統中有一個唯一的標識用以在活動重啓時還原該片段(並且用於獲取該片段以執行如移除該片段之類的操作)。有三種方式提供一個片段的ID:

    • android:id屬性提供唯一的ID。
    • android:tag屬性提供唯一的字符串。
    • 如果沒有提供以上兩種標識,系統將使用其容器視圖的ID。

 

  • 或者,在程序中將片段添加至已有的ViewGroup。

在活動正在運行中的任意時刻,都可以將片段添加至活動的佈局。只需要指定這一需要放置片段的ViewGroup即可。

要在活動中進行片段事務(例如添加、移除或替換一個片段),必須使用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()的第一個參數是該片段應當被放置的ViewGroup,通過資源ID來指定,第二個參數則是要添加的片段。

一旦通過FragmentTransaction進行了變更,比如調用commit()來使變更生效。

添加沒有UI的片段

上面的範例演示瞭如何添加提供了UI的片段至活動中。不過,也可以通過使用一個片段來在活動中執行後臺行爲而不顯示額外的UI。

要添加沒有UI的片段,需要在活動中通過使用add(Fragment, String)來完成(爲片段提供一個唯一的”tag”字符串而非一個視圖ID)。這樣就能添加該片段,但是,因爲它沒有和活動佈局中的某個視圖相關聯,所以將不會收到onCreateView()的調用。因此不需要去實現這個方法。

爲片段提供一個字符串標籤(tag)對於無UI片段來說並不是很嚴格的要求——也能爲包含UI的片段提供標籤——但如果片段不含UI,那麼字符串標籤就是唯一能識別該片段的方式了。如果希望之後能從活動中獲取該片段,需要使用findFragmentByTag()。

關於將片段作爲後臺工作部件而不含UI的活動的範例,請參見FragmentRetainInstance.java範例。

管理片段

要在活動中管理片段,需要使用FragmentManager。可以通過在活動中調用getFragmentManager()來獲取它。

藉助FragmentManager可以做到包括以下這些事:

  • 通過findFragmentById()(適用於在活動佈局中提供了UI的片段)或findFragmentByTag()(對於提供了或沒有提供UI的片段都適用)來獲取已存在於活動之中的片段。
  • 通過popBackStack()將片段從返回棧中彈出(模擬了一次用戶按下BACK鍵的指令)。
  • 通過addOnBackStackChangedListener()爲返回棧的變化註冊一個監聽器。

關於這些方法的更多信息,請參見FragmentManager類的文檔。

正如在之前一節中所描述的,還可以使用FragmentManager來打開一個FragmentTransaction,以能夠執行例如添加、移除片段等事務。

執行片段事務(Fragment Transaction)

在活動中使用片段的一大特點是可以根據用戶交互對這些片段進行添加、移除、替換或是執行其他操作。對活動進行的每一組改變都被稱爲是一次事務(transaction),可以通過FragmentTransaction提供的API執行事務。還可以把每一個事務都保存至活動所管理的返回棧,使得用戶可以撤銷片段的改變(和返回上一個活動類似)。

可以像這樣從FragmentManager獲取一個FragmentTransaction的實例:

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

每一個事務都是一組能同時執行的變更。可以通過對給定操作使用如add()、remove()和replace()等方法在設置所有希望執行的變更。之後,必須對活動調用commit()來應用該事務。

不過,在調用commit()之前,需要調用addToBackStack(),以將該事務加入片段事務的返回棧中。該返回棧由活動所管理,允許用戶通過按下返回鍵來返回到之前的片段狀態。

例如,下面展示瞭如何替換一個片段,並將之前的狀態保存於返回棧中:

// 創建新的片段和事務
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// 用該片段替換fragment_container視圖中所含有的任意片段,
// 並將該事務添加至返回棧
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// 執行該事務
transaction.commit();

在這個範例裏,newFragment替代了當前在由ID R.id.fragment_container定義的佈局容器中的(任意的)片段。通過調用addToBackStack(),替換事務被保存於返回棧中,因此用戶可以通過按下返回鍵來回滾事務,回到之前的片段。

如果將多次變更添加至事務(例如第二個add()或是remove())並調用addToBackStack()的話,那麼所有在調用commit()之前被應用的變更都將作爲一個單獨的事務被加入返回棧中,BACK鍵將會恢復所有這些變更。

向FragmentTransaction中添加變更的順序是無所謂的,不過:

  • 必須在最後調用commit()
  • 如果將多個片段加入同一個容器中,那麼添加的順序將決定它們在視圖層級中的順序

如果在執行移除片段的事務時沒有調用addToBackStack(),那麼該片段將在事務被執行(commit)後被銷燬,用戶無法再次返回它。反之,如果在移除片段時調用了addToBackStack(),那麼該片段將被中止(stop),並在用戶返回時被繼續(resume)。

提示:對於每個片段事務,都可以通過在執行(commit)之前調用setTranstition()以應用一個切換動畫。

調用commit()並不會立即執行事務。它只是作了在活動的UI線程(“主”線程)準備好之時運行該事務的調度。不過,如有必要,可以在UI線程中調用executePendingTransactions()來立即執行由commit()提交的事務。只有在該事務是其他線程工作的組成部分時纔有必要這麼做。

注意:可以僅在活動保存其狀態之前(當用戶離開活動時)使用commit()來執行一次事務。如果在此之後執行,將拋出一個異常。這是因爲如果活動需要被儲存的話,執行之後的狀態將無法被保存。對於丟失狀態也無妨的情況,則需使用commitAllowingStateLoss()。

和活動進行通信

儘管一個Fragment被作爲一個獨立於Activity的對象使用,且可以被用於多個活動之中,一個給定的片段實例可以與包含它的活動直接關聯。

特別要注意的是,片段可以以getActivity()來獲取Activity的實例,並能夠在活動的佈局中很容易地進行尋找視圖之類的任務:

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

類似的,活動可以通過findFragmentById()或findFragmentByTag()從FragmentManager獲取一個Fragment的引用來調用片段內的方法。例如:

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

創建活動接受的事件回饋

在有些情況下,可能會需要片段與活動共享事件。這可以通過在片段定義一個回饋接口並在宿主活動中實現它來實現。當活動通過該接口接收到了一個回饋時,如有需要,它可以與佈局中的其他片段共享信息。

例如,如果一個新的應用程序在一個活動中有兩個片段——一個用來顯示一列文章(片段A)而另一個用來顯示某一篇文章(片段B)——那麼片段A必須在某一個列表項目被選中時告知活動,使活動能夠告知片段B來顯示該文章。在這種情況下,OnArticleSelectedListener接口被聲明於片段A內:

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

之後持有該片段的活動實現了OnArticleSelectedListener接口並覆蓋了onArticleSelected()來將來自於片段A的事件通知給片段B。爲確保宿主活動實現了該接口,片段A的onAttach()回饋方法(當把片段添加至活動時系統將會自動調用該方法)通過將傳遞過來的Activity強制轉換爲了onAttach()以實例化了一個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");
        }
    }
    ...
}

如果該活動沒有實現這個接口,那麼片段將會拋出一個ClassCastException。一旦成功實現了該接口,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,它被活動(或其他片段)用來從應用程序的ContentProvider中獲取文章。

關於使用內容提供器的更多信息請參見“Content Providers文檔”。

添加項目至動作條(Action Bar)

片段可以通過實現onCreateOptionsMenu()方法來和活動的選項菜單(Options Menu)項目相結合使用(因此也就可以和動作條一起使用)。不過,爲了使該方法能夠接受調用,必須在onCreate()中調用setHasOptionsMenu()來標識該片段將被添加至選項菜單(否則,該片段將不會接收到對onCreateOptionsMenu()的調用)

之後從片段添加至選項菜單的任何項目都將被增加到已有菜單項目之後。該片段還將在某一菜單項目被選中時收到onOptionsItemSelected()的回饋。

也可以在片段佈局中註冊一個視圖,通過調用registerForContextMenu()來提供上下文菜單。當用戶打開上下文菜單時,該片段將收到一個對onCreateContextMenu()的調用。當用戶選中一個項目時,片段將收到一個對onContextItemSelected()的調用。

注意:儘管片段對每一個添加過的項目都能收到一個項目選中回饋,但活動本身將在用戶選擇菜單項目時首先收到相應的回饋。如果活動的項目選中回饋的實現沒有處理該選中的項目,那麼該事件纔會被傳遞給片段的回饋方法。對於選項菜單和上下文菜單來說都是如此。

關於菜單的更多信息,請參見“菜單”和“動作條”開發者指南。

處理片段生命週期

管理一個片段的生命週期和管理一個活動的生命週期十分的相像。如同活動一樣,一個片段可以以三種狀態存在:

Resumed

該片段在運行中的活動裏是可見的。

Paused

另一個活動正處於前臺且獲得了焦點,不過含有該片段的活動依然是可見的(前臺的活動是半透明的或沒有覆蓋整個屏幕 )。

Stopped

片段不可見。或是宿主活動被停止了,或是片段被從活動中移除後加入了返回棧。一個被停止的片段依然是存在着的(所有的狀態和成員信息由系統保持着)。不過,它對於用戶不再可見,在活動被殺除後也將被殺除。

和一個活動一樣,可以通過一個Bundle以在活動的進程被殺除而又需要在該活動被重建時還原該片段的情況下保存片段的狀態。可以在活動的onSaveInstanceState()回饋方法中保存狀態,在onCreate()、onCreateView()或onActivityCreated()中還原狀態。關於保存狀態的更多信息,請參見“活動”文檔。

圖 3. 活動生命週期對於片段生命週期的影響

一個活動和一個片段的生命週期之間最大的區別在於它們是如何被保存於各自的返回棧中的。一個活動默認爲在其被停止時被放入由系統所管理的一個返回棧中(這樣用戶就能通過BACK鍵返回該活動,就像“任務和返回棧”中所說的那樣)。然而, 一個片段只有在進行片段移除的事務中調用addToBackStack()來顯式地請求保存片段的實例時纔會被放入由宿主活動所管理的返回棧中。

除此之外,管理片段的生命週期和管理活動的生命週期十分相似。因此,管理活動生命週期時的做法同樣可以應用於管理片段。所需要理解的就僅僅是活動的生命週期將會地片段的生命週期有着怎樣的影響。

範例

爲了總結本文檔中所說的,下面有一個範例來演示一個使用了兩個片段的活動是如何創建一個雙面板佈局的。下面的這個活動包含了一個顯示一列莎士比亞戲劇標題的片段和另一個在從列表中選擇項目後顯示該戲劇的摘要的片段。它還演示瞭如何基於不同的屏幕配置來提供不同的片段配置。

注意:該活動的完整源代碼可以在FragmentLayout.java中得到。

主活動按照常規的方式在onCreate()中應用佈局:

@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>

使用該佈局,系統將在活動載入該佈局時實例化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。這意味着當設備處於豎向狀態時,只有戲劇標題列表是可見的。因此,當用戶在這時點擊了列表項目的話,程序會啓動一個新的活動來顯示摘要,而不是載入第二個片段。

接下來,看一下如何在片段類中完成整個過程。首先是TitlesFragment,它顯示了一列莎士比亞戲劇標題。該片段繼承於ListFragment,通過它來處理大部分的列表視圖工作。

正如代碼中所表明的,需要注意當用戶點擊一個列表項目時有兩種可能的行爲:這取決於兩種佈局中的哪一種使處於激活狀態的。它既可以在同一個活動中創建並表示一個新的片段來顯示詳細信息(將片段加入FrameLayout),也可以啓動一個新的活動(並在那裏顯示片段)。

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();
                ft.replace(R.id.details, 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();
        }
    }
}

注意這個活動在橫屏模式時將會自動結束,改爲由主活動同時顯示DetailsFragmentTitlesFragment。而在用戶以豎屏模式啓動DetailsActivity之後旋轉屏幕至橫屏模式的情況下也會如此(此時將重啓當前活動)。

關於使用片段的更多範例(以及本範例的完整源文件),請參見ApiDemos裏的範例代碼(可以從範例SDK組件中下載)。

本頁部分內容根據Android Open Source Project創作並共享的內容修改,並在知識共享 署名2.5許可協議中所述條款的限制下使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章