[文章內(nèi)容來(lái)自Developers]
片段
Fragment表示 Activity中的行為或用戶界面部分。您可以將多個(gè)片段組合在一個(gè) Activity 中來(lái)構(gòu)建多窗格 UI,以及在多個(gè) Activity 中重復(fù)使用某個(gè)片段。您可以將片段視為 Activity 的模塊化組成部分,它具有自己的生命周期,能接收自己的輸入事件,并且您可以在 Activity 運(yùn)行時(shí)添加或移除片段(有點(diǎn)像您可以在不同 Activity 中重復(fù)使用的“子 Activity”)。
片段必須始終嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影響。 例如,當(dāng) Activity 暫停時(shí),其中的所有片段也會(huì)暫停;當(dāng) Activity 被銷毀時(shí),所有片段也會(huì)被銷毀。 不過(guò),當(dāng) Activity 正在運(yùn)行(處于已恢復(fù)生命周期狀態(tài))時(shí),您可以獨(dú)立操縱每個(gè)片段,如添加或移除它們。 當(dāng)您執(zhí)行此類片段事務(wù)時(shí),您也可以將其添加到由 Activity 管理的返回棧 — Activity 中的每個(gè)返回棧條目都是一條已發(fā)生片段事務(wù)的記錄。 返回棧讓用戶可以通過(guò)按返回按鈕撤消片段事務(wù)(后退)。
當(dāng)您將片段作為 Activity 布局的一部分添加時(shí),它存在于 Activity 視圖層次結(jié)構(gòu)的某個(gè) ViewGroup內(nèi)部,并且片段會(huì)定義其自己的視圖布局。您可以通過(guò)在 Activity 的布局文件中聲明片段,將其作為<fragment>元素插入您的 Activity 布局中,或者通過(guò)將其添加到某個(gè)現(xiàn)有 ViewGroup,利用應(yīng)用代碼進(jìn)行插入。不過(guò),片段并非必須成為 Activity 布局的一部分;您還可以將沒(méi)有自己 UI 的片段用作 Activity 的不可見(jiàn)工作線程。
本文描述如何在開(kāi)發(fā)您的應(yīng)用時(shí)使用片段,包括將片段添加到 Activity 返回棧時(shí)如何保持其狀態(tài)、如何與 Activity 及 Activity 中的其他片段共享事件、如何為 Activity 的操作欄發(fā)揮作用等等。
設(shè)計(jì)原理
Android 在 Android 3.0(API 級(jí)別 11)中引入了片段,主要是為了給大屏幕(如平板電腦)上更加動(dòng)態(tài)和靈活的 UI 設(shè)計(jì)提供支持。由于平板電腦的屏幕比手機(jī)屏幕大得多,因此可用于組合和交換 UI 組件的空間更大。利用片段實(shí)現(xiàn)此類設(shè)計(jì)時(shí),您無(wú)需管理對(duì)視圖層次結(jié)構(gòu)的復(fù)雜更改。 通過(guò)將 Activity 布局分成片段,您可以在運(yùn)行時(shí)修改 Activity 的外觀,并在由 Activity 管理的返回棧中保留這些更改。
例如,新聞應(yīng)用可以使用一個(gè)片段在左側(cè)顯示文章列表,使用另一個(gè)片段在右側(cè)顯示文章 — 兩個(gè)片段并排顯示在一個(gè) Activity 中,每個(gè)片段都具有自己的一套生命周期回調(diào)方法,并各自處理自己的用戶輸入事件。 因此,用戶不需要使用一個(gè) Activity 來(lái)選擇文章,然后使用另一個(gè) Activity 來(lái)閱讀文章,而是可以在同一個(gè) Activity 內(nèi)選擇文章并進(jìn)行閱讀,如圖 1 中的平板電腦布局所示。
您應(yīng)該將每個(gè)片段都設(shè)計(jì)為可重復(fù)使用的模塊化 Activity 組件。也就是說(shuō),由于每個(gè)片段都會(huì)通過(guò)各自的生命周期回調(diào)來(lái)定義其自己的布局和行為,您可以將一個(gè)片段加入多個(gè) Activity,因此,您應(yīng)該采用可復(fù)用式設(shè)計(jì),避免直接從某個(gè)片段直接操縱另一個(gè)片段。 這特別重要,因?yàn)槟K化片段讓您可以通過(guò)更改片段的組合方式來(lái)適應(yīng)不同的屏幕尺寸。 在設(shè)計(jì)可同時(shí)支持平板電腦和手機(jī)的應(yīng)用時(shí),您可以在不同的布局配置中重復(fù)使用您的片段,以根據(jù)可用的屏幕空間優(yōu)化用戶體驗(yàn)。 例如,在手機(jī)上,如果不能在同一 Activity 內(nèi)儲(chǔ)存多個(gè)片段,可能必須利用單獨(dú)片段來(lái)實(shí)現(xiàn)單窗格 UI。

例如 — 仍然以新聞應(yīng)用為例 — 在平板電腦尺寸的設(shè)備上運(yùn)行時(shí),該應(yīng)用可以在 Activity A 中嵌入兩個(gè)片段。 不過(guò),在手機(jī)尺寸的屏幕上,沒(méi)有足以儲(chǔ)存兩個(gè)片段的空間,因此Activity A 只包括用于顯示文章列表的片段,當(dāng)用戶選擇文章時(shí),它會(huì)啟動(dòng)Activity B,其中包括用于閱讀文章的第二個(gè)片段。 因此,應(yīng)用可通過(guò)重復(fù)使用不同組合的片段來(lái)同時(shí)支持平板電腦和手機(jī),如圖 1 所示。
創(chuàng)建片段

要想創(chuàng)建片段,您必須創(chuàng)建 Fragment的子類(或已有其子類)。Fragment類的代碼與Activity非常相似。它包含與 Activity 類似的回調(diào)方法,如onCreate()、onStart()、onPause()和 onStop()。實(shí)際上,如果您要將現(xiàn)有 Android 應(yīng)用轉(zhuǎn)換為使用片段,可能只需將代碼從 Activity 的回調(diào)方法移入片段相應(yīng)的回調(diào)方法中。
通常,您至少應(yīng)實(shí)現(xiàn)以下生命周期方法:
onCreate()
系統(tǒng)會(huì)在創(chuàng)建片段時(shí)調(diào)用此方法。您應(yīng)該在實(shí)現(xiàn)內(nèi)初始化您想在片段暫停或停止后恢復(fù)時(shí)保留的必需片段組件。
onCreateView()
系統(tǒng)會(huì)在片段首次繪制其用戶界面時(shí)調(diào)用此方法。 要想為您的片段繪制 UI,您從此方法中返回的 View必須是片段布局的根視圖。如果片段未提供 UI,您可以返回 null。
onPause()
系統(tǒng)將此方法作為用戶離開(kāi)片段的第一個(gè)信號(hào)(但并不總是意味著此片段會(huì)被銷毀)進(jìn)行調(diào)用。 您通常應(yīng)該在此方法內(nèi)確認(rèn)在當(dāng)前用戶會(huì)話結(jié)束后仍然有效的任何更改(因?yàn)橛脩艨赡懿粫?huì)返回)。
大多數(shù)應(yīng)用都應(yīng)該至少為每個(gè)片段實(shí)現(xiàn)這三個(gè)方法,但您還應(yīng)該使用幾種其他回調(diào)方法來(lái)處理片段生命周期的各個(gè)階段。 處理片段生命周期您可能還想擴(kuò)展幾個(gè)子類,而不是 Fragment基類:
- DialogFragment顯示浮動(dòng)對(duì)話框。使用此類創(chuàng)建對(duì)話框可有效地替代使用 Activity類中的對(duì)話框幫助程序方法,因?yàn)槟梢詫⑵螌?duì)話框納入由 Activity 管理的片段返回棧,從而使用戶能夠返回清除的片段。
- ListFragment顯示由適配器(如 SimpleCursorAdapter)管理的一系列項(xiàng)目,類似于 ListActivity。它提供了幾種管理列表視圖的方法,如用于處理點(diǎn)擊事件的onListItemClick()回調(diào)。
- PreferenceFragment以列表形式顯示 Preference對(duì)象的層次結(jié)構(gòu),類似于 PreferenceActivity。這在為您的應(yīng)用創(chuàng)建“設(shè)置” Activity 時(shí)很有用處。
添加用戶界面
片段通常用作 Activity 用戶界面的一部分,將其自己的布局融入 Activity。
要想為片段提供布局,您必須實(shí)現(xiàn) onCreateView()回調(diào)方法,Android 系統(tǒng)會(huì)在片段需要繪制其布局時(shí)調(diào)用該方法。您對(duì)此方法的實(shí)現(xiàn)返回的View必須是片段布局的根視圖。
注:如果您的片段是 ListFragment的子類,則默認(rèn)實(shí)現(xiàn)會(huì)從 onCreateView()返回一個(gè) ListView,因此您無(wú)需實(shí)現(xiàn)它。
要想從 onCreateView()返回布局,您可以通過(guò) XML 中定義的布局資源來(lái)擴(kuò)展布局。為幫助您執(zhí)行此操作,onCreateView()提供了一個(gè)LayoutInflater對(duì)象。
例如,以下這個(gè) 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);
}}
創(chuàng)建布局
在上例中,R.layout.example_fragment是對(duì)應(yīng)用資源中保存的名為example_fragment.xml的布局資源的引用。
傳遞至 onCreateView()的 container參數(shù)是您的片段布局將插入到的父 ViewGroup(來(lái)自 Activity 的布局)。
savedInstanceState參數(shù)是在恢復(fù)片段時(shí),提供上一片段實(shí)例相關(guān)數(shù)據(jù)的Bundle。
inflate()方法帶有三個(gè)參數(shù):
- 您想要擴(kuò)展的布局的資源 ID;
- 將作為擴(kuò)展布局父項(xiàng)的 ViewGroup。傳遞 container對(duì)系統(tǒng)向擴(kuò)展布局的根視圖(由其所屬的父視圖指定)應(yīng)用布局參數(shù)具有重要意義;
- 指示是否應(yīng)該在擴(kuò)展期間將擴(kuò)展布局附加至 ViewGroup(第二個(gè)參數(shù))的布爾值。(在本例中,其值為 false,因?yàn)橄到y(tǒng)已經(jīng)將擴(kuò)展布局插入 container — 傳遞 true 值會(huì)在最終布局中創(chuàng)建一個(gè)多余的視圖組。)
現(xiàn)在,您已經(jīng)了解了如何創(chuàng)建提供布局的片段。接下來(lái),您需要將該片段添加到您的 Activity 中。
向 Activity 添加片段
通常,片段向宿主 Activity 貢獻(xiàn)一部分 UI,作為 Activity 總體視圖層次結(jié)構(gòu)的一部分嵌入到 Activity 中??梢酝ㄟ^(guò)兩種方式向 Activity 布局添加片段:
在 Activity 的布局文件內(nèi)聲明片段在本例中,您可以將片段當(dāng)作視圖來(lái)為其指定布局屬性。 例如,以下是一個(gè)具有兩個(gè)片段的 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屬性指定要在布局中實(shí)例化的 Fragment類。
當(dāng)系統(tǒng)創(chuàng)建此 Activity 布局時(shí),會(huì)實(shí)例化在布局中指定的每個(gè)片段,并為每個(gè)片段調(diào)用 onCreateView()方法,以檢索每個(gè)片段的布局。系統(tǒng)會(huì)直接插入片段返回的 View來(lái)替代 <fragment>元素。
注:每個(gè)片段都需要一個(gè)唯一的標(biāo)識(shí)符,重啟 Activity 時(shí),系統(tǒng)可以使用該標(biāo)識(shí)符來(lái)恢復(fù)片段(您也可以使用該標(biāo)識(shí)符來(lái)捕獲片段以執(zhí)行某些事務(wù),如將其移除)。 可以通過(guò)三種方式為片段提供 ID:
- 為 android:id屬性提供唯一 ID。
- 為 android:tag屬性提供唯一字符串。
- 如果您未給以上兩個(gè)屬性提供值,系統(tǒng)會(huì)使用容器視圖的 ID。
或者通過(guò)編程方式將片段添加到某個(gè)現(xiàn)有 ViewGroup您可以在 Activity 運(yùn)行期間隨時(shí)將片段添加到 Activity 布局中。您只需指定要將片段放入哪個(gè) ViewGroup。
要想在您的 Activity 中執(zhí)行片段事務(wù)(如添加、移除或替換片段),您必須使用 FragmentTransaction 中的 API。您可以像下面這樣從Activity獲取一個(gè) FragmentTransaction實(shí)例:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
然后,您可以使用 add()方法添加一個(gè)片段,指定要添加的片段以及將其插入哪個(gè)視圖。例如:
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
傳遞到 add()的第一個(gè)參數(shù)是 ViewGroup,即應(yīng)該放置片段的位置,由資源 ID 指定,第二個(gè)參數(shù)是要添加的片段。
一旦您通過(guò) FragmentTransaction 做出了更改,就必須調(diào)用 commit()以使更改生效。
添加沒(méi)有 UI 的片段
上例展示了如何向您的 Activity 添加片段以提供 UI。不過(guò),您還可以使用片段為 Activity 提供后臺(tái)行為,而不顯示額外 UI。
要想添加沒(méi)有 UI 的片段,請(qǐng)使用 add(Fragment, String)從 Activity 添加片段(為片段提供一個(gè)唯一的字符串“標(biāo)記”,而不是視圖 ID)。 這會(huì)添加片段,但由于它并不與 Activity 布局中的視圖關(guān)聯(lián),因此不會(huì)收到對(duì) onCreateView()的調(diào)用。因此,您不需要實(shí)現(xiàn)該方法。
并非只能為非 UI 片段提供字符串標(biāo)記 — 您也可以為具有 UI 的片段提供字符串標(biāo)記 — 但如果片段沒(méi)有 UI,則字符串標(biāo)記將是標(biāo)識(shí)它的唯一方式。如果您想稍后從 Activity 中獲取片段,則需要使用 findFragmentByTag()。
如需查看將沒(méi)有 UI 的片段用作后臺(tái)工作線程的示例 Activity,請(qǐng)參閱 FragmentRetainInstance.java示例,該示例包括在 SDK 示例(通過(guò) Android SDK 管理器提供)中,以<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java形式位于您的系統(tǒng)中。
管理片段
要想管理您的 Activity 中的片段,您需要用 FragmentManager。要想獲取它,請(qǐng)從您的 Activity 調(diào)用 getFragmentManager()。您可以使用 FragmentManager執(zhí)行的操作包括:
- 通過(guò) findFragmentById()(對(duì)于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(對(duì)于提供或不提供 UI 的片段)獲取 Activity 中存在的片段。
- 通過(guò) popBackStack()(模擬用戶發(fā)出的返回命令)將片段從返回棧中彈出。
- 通過(guò) addOnBackStackChangedListener()注冊(cè)一個(gè)偵聽(tīng)返回棧變化的偵聽(tīng)器。
如上文所示,您也可以使用 FragmentManager打開(kāi)一個(gè) FragmentTransaction,通過(guò)它來(lái)執(zhí)行某些事務(wù),如添加和移除片段。
** 執(zhí)行片段事務(wù)**
在 Activity 中使用片段的一大優(yōu)點(diǎn)是,可以根據(jù)用戶行為通過(guò)它們執(zhí)行添加、移除、替換以及其他操作。 您提交給 Activity 的每組更改都稱為事務(wù),您可以使用 FragmentTransaction中的 API 來(lái)執(zhí)行一項(xiàng)事務(wù)。您也可以將每個(gè)事務(wù)保存到由 Activity 管理的返回棧內(nèi),從而讓用戶能夠回退片段更改(類似于回退 Activity)。
您可以像下面這樣從 FragmentManager獲取一個(gè) FragmentTransaction實(shí)例:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每個(gè)事務(wù)都是您想要同時(shí)執(zhí)行的一組更改。您可以使用 add()、remove()和 replace()等方法為給定事務(wù)設(shè)置您想要執(zhí)行的所有更改。然后,要想將事務(wù)應(yīng)用到 Activity,您必須調(diào)用 commit()。
不過(guò),在您調(diào)用 commit()之前,您可能想調(diào)用 addToBackStack(),以將事務(wù)添加到片段事務(wù)返回棧。 該返回棧由 Activity 管理,允許用戶通過(guò)按返回按鈕返回上一片段狀態(tài)。
例如,以下示例說(shuō)明了如何將一個(gè)片段替換成另一個(gè)片段,以及如何在返回棧中保留先前狀態(tài):
// 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
stacktransaction.replace(R.id.fragment_container, newFragment);transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
在上例中,newFragment
會(huì)替換目前在 R.id.fragment_container
ID 所標(biāo)識(shí)的布局容器中的任何片段(如有)。通過(guò)調(diào)用 addToBackStack()可將替換事務(wù)保存到返回棧,以便用戶能夠通過(guò)按返回按鈕撤消事務(wù)并回退到上一片段。
如果您向事務(wù)添加了多個(gè)更改(如又一個(gè) add()或 remove()),并且調(diào)用了 addToBackStack(),則在調(diào)用 commit()前應(yīng)用的所有更改都將作為單一事務(wù)添加到返回棧,并且返回按鈕會(huì)將它們一并撤消。
向 FragmentTransaction添加更改的順序無(wú)關(guān)緊要,不過(guò):
您必須最后調(diào)用 commit()
如果您要向同一容器添加多個(gè)片段,則您添加片段的順序?qū)Q定它們?cè)谝晥D層次結(jié)構(gòu)中的出現(xiàn)順序
如果您沒(méi)有在執(zhí)行移除片段的事務(wù)時(shí)調(diào)用 addToBackStack(),則事務(wù)提交時(shí)該片段會(huì)被銷毀,用戶將無(wú)法回退到該片段。 不過(guò),如果您在刪除片段時(shí)調(diào)用了 addToBackStack(),則系統(tǒng)會(huì)停止該片段,并在用戶回退時(shí)將其恢復(fù)。
提示:對(duì)于每個(gè)片段事務(wù),您都可以通過(guò)在提交前調(diào)用 setTransition()來(lái)應(yīng)用過(guò)渡動(dòng)畫。
調(diào)用 commit()不會(huì)立即執(zhí)行事務(wù),而是在 Activity 的 UI 線程(“主”線程)可以執(zhí)行該操作時(shí)再安排其在線程上運(yùn)行。不過(guò),如有必要,您也可以從 UI 線程調(diào)用 executePendingTransactions() 以立即執(zhí)行 commit() 提交的事務(wù)。通常不必這樣做,除非其他線程中的作業(yè)依賴該事務(wù)。
注意:您只能在 Activity[保存其狀態(tài)(用戶離開(kāi) Activity)之前使用 commit() 提交事務(wù)。如果您試圖在該時(shí)間點(diǎn)后提交,則會(huì)引發(fā)異常。 這是因?yàn)槿缧杌謴?fù) Activity,則提交后的狀態(tài)可能會(huì)丟失。 對(duì)于丟失提交無(wú)關(guān)緊要的情況,請(qǐng)使用 commitAllowingStateLoss()。
與 Activity 通信
盡管 Fragment是作為獨(dú)立于 Activity的對(duì)象實(shí)現(xiàn),并且可在多個(gè) Activity 內(nèi)使用,但片段的給定實(shí)例會(huì)直接綁定到包含它的 Activity。
具體地說(shuō),片段可以通過(guò) getActivity()訪問(wèn) Activity實(shí)例,并輕松地執(zhí)行在 Activity 布局中查找視圖等任務(wù)。
View listView = getActivity().findViewById(R.id.list);
同樣地,您的 Activity 也可以使用 findFragmentById()或 findFragmentByTag(),通過(guò)從 FragmentManager獲取對(duì) Fragment的引用來(lái)調(diào)用片段中的方法。例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
創(chuàng)建對(duì) Activity 的事件回調(diào)
在某些情況下,您可能需要通過(guò)片段與 Activity 共享事件。執(zhí)行此操作的一個(gè)好方法是,在片段內(nèi)定義一個(gè)回調(diào)接口,并要求宿主 Activity 實(shí)現(xiàn)它。 當(dāng) Activity 通過(guò)該接口收到回調(diào)時(shí),可以根據(jù)需要與布局中的其他片段共享這些信息。
例如,如果一個(gè)新聞應(yīng)用的 Activity 有兩個(gè)片段 — 一個(gè)用于顯示文章列表(片段 A),另一個(gè)用于顯示文章(片段 B)— 那么片段 A 必須在列表項(xiàng)被選定后告知 Activity,以便它告知片段 B 顯示該文章。 在本例中,OnArticleSelectedListener
接口在片段 A 內(nèi)聲明:
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
然后,該片段的宿主 Activity 會(huì)實(shí)現(xiàn) OnArticleSelectedListener
接口并替代 onArticleSelected()
,將來(lái)自片段 A 的事件通知片段 B。為確保宿主 Activity 實(shí)現(xiàn)此接口,片段 A 的 onAttach() 回調(diào)方法(系統(tǒng)在向 Activity 添加片段時(shí)調(diào)用的方法)會(huì)通過(guò)轉(zhuǎn)換傳遞到 onAttach() 中的 Activity來(lái)實(shí)例化 OnArticleSelectedListener的實(shí)例:
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 未實(shí)現(xiàn)接口,則片段會(huì)引發(fā) ClassCastException。實(shí)現(xiàn)時(shí),mListener成員會(huì)保留對(duì) Activity 的 OnArticleSelectedListener實(shí)現(xiàn)的引用,以便片段 A 可以通過(guò)調(diào)用 OnArticleSelectedListener接口定義的方法與 Activity 共享事件。例如,如果片段 A 是ListFragment的一個(gè)擴(kuò)展,則用戶每次點(diǎn)擊列表項(xiàng)時(shí),系統(tǒng)都會(huì)調(diào)用片段中的 onListItemClick(),然后該方法會(huì)調(diào)用 onArticleSelected()以與 Activity 共享事件:
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
參數(shù)是被點(diǎn)擊項(xiàng)的行 ID,即 Activity(或其他片段)用來(lái)從應(yīng)用的 ContentProvider獲取文章的 ID。
向應(yīng)用欄添加項(xiàng)目
您的片段可以通過(guò)實(shí)現(xiàn) onCreateOptionsMenu()向 Activity 的選項(xiàng)菜單(并因此向應(yīng)用欄貢獻(xiàn)菜單項(xiàng)。不過(guò),為了使此方法能夠收到調(diào)用,您必須在 onCreate()期間調(diào)用 setHasOptionsMenu(),以指示片段想要向選項(xiàng)菜單添加菜單項(xiàng)(否則,片段將不會(huì)收到對(duì)onCreateOptionsMenu()的調(diào)用)。
您之后從片段添加到選項(xiàng)菜單的任何菜單項(xiàng)都將追加到現(xiàn)有菜單項(xiàng)之后。 選定菜單項(xiàng)時(shí),片段還會(huì)收到對(duì) onOptionsItemSelected()的回調(diào)。
您還可以通過(guò)調(diào)用 registerForContextMenu(),在片段布局中注冊(cè)一個(gè)視圖來(lái)提供上下文菜單。用戶打開(kāi)上下文菜單時(shí),片段會(huì)收到對(duì)onCreateContextMenu()的調(diào)用。當(dāng)用戶選擇某個(gè)菜單項(xiàng)時(shí),片段會(huì)收到對(duì) onContextItemSelected()的調(diào)用。
注:盡管您的片段會(huì)收到與其添加的每個(gè)菜單項(xiàng)對(duì)應(yīng)的菜單項(xiàng)選定回調(diào),但當(dāng)用戶選擇菜單項(xiàng)時(shí),Activity 會(huì)首先收到相應(yīng)的回調(diào)。 如果 Activity 對(duì)菜單項(xiàng)選定回調(diào)的實(shí)現(xiàn)不會(huì)處理選定的菜單項(xiàng),則系統(tǒng)會(huì)將事件傳遞到片段的回調(diào)。 這適用于選項(xiàng)菜單和上下文菜單。
處理片段生命周期

管理片段生命周期與管理 Activity 生命周期很相似。和 Activity 一樣,片段也以三種狀態(tài)存在:
繼續(xù)
片段在運(yùn)行中的 Activity 中可見(jiàn)。
暫停
另一個(gè) Activity 位于前臺(tái)并具有焦點(diǎn),但此片段所在的 Activity 仍然可見(jiàn)(前臺(tái) Activity 部分透明,或未覆蓋整個(gè)屏幕)。
停止
片段不可見(jiàn)。宿主 Activity 已停止,或片段已從 Activity 中移除,但已添加到返回棧。 停止片段仍然處于活動(dòng)狀態(tài)(系統(tǒng)會(huì)保留所有狀態(tài)和成員信息)。 不過(guò),它對(duì)用戶不再可見(jiàn),如果 Activity 被終止,它也會(huì)被終止。
同樣與 Activity 一樣,假使 Activity 的進(jìn)程被終止,而您需要在重建 Activity 時(shí)恢復(fù)片段狀態(tài),您也可以使用 Bundle 保留片段的狀態(tài)。您可以在片段的 onSaveInstanceState()回調(diào)期間保存狀態(tài),并可在onCreate()、onCreateView()或 onActivityCreated()期間恢復(fù)狀態(tài)。
Activity 生命周期與片段生命周期之間的最顯著差異在于它們?cè)谄涓髯苑祷貤V械拇鎯?chǔ)方式。 默認(rèn)情況下,Activity 停止時(shí)會(huì)被放入由系統(tǒng)管理的 Activity 返回棧(以便用戶通過(guò)返回按鈕回退到 Activity,任務(wù)和返回棧對(duì)此做了闡述)。不過(guò),僅當(dāng)您在移除片段的事務(wù)執(zhí)行期間通過(guò)調(diào)用 addToBackStack()顯式請(qǐng)求保存實(shí)例時(shí),系統(tǒng)才會(huì)將片段放入由宿主 Activity 管理的返回棧。
在其他方面,管理片段生命周期與管理 Activity 生命周期非常相似。 因此,管理 Activity 生命周期的做法同樣適用于片段。 但您還需要了解 Activity 的生命周期對(duì)片段生命周期的影響。
注意:如需 Fragment 內(nèi)的某個(gè) Context對(duì)象,可以調(diào)用 getActivity()。但要注意,請(qǐng)僅在片段附加到 Activity 時(shí)調(diào)用getActivity()。如果片段尚未附加,或在其生命周期結(jié)束期間分離,則 getActivity()將返回 null。
與 Activity 生命周期協(xié)調(diào)一致
片段所在的 Activity 的生命周期會(huì)直接影響片段的生命周期,其表現(xiàn)為,Activity 的每次生命周期回調(diào)都會(huì)引發(fā)每個(gè)片段的類似回調(diào)。 例如,當(dāng) Activity 收到 onPause()時(shí),Activity 中的每個(gè)片段也會(huì)收到 onPause()。
不過(guò),片段還有幾個(gè)額外的生命周期回調(diào),用于處理與 Activity 的唯一交互,以執(zhí)行構(gòu)建和銷毀片段 UI 等操作。 這些額外的回調(diào)方法是:
- onAttach()
在片段已與 Activity 關(guān)聯(lián)時(shí)調(diào)用(Activity傳遞到此方法內(nèi))。 - onCreateView()
調(diào)用它可創(chuàng)建與片段關(guān)聯(lián)的視圖層次結(jié)構(gòu)。 - onActivityCreated()
在 Activity 的 onCreate()方法已返回時(shí)調(diào)用。 - onDestroyView()
在移除與片段關(guān)聯(lián)的視圖層次結(jié)構(gòu)時(shí)調(diào)用。 - onDetach()
在取消片段與 Activity 的關(guān)聯(lián)時(shí)調(diào)用。
圖 3 圖示說(shuō)明了受其宿主 Activity 影響的片段生命周期流。在該圖中,您可以看到 Activity 的每個(gè)連續(xù)狀態(tài)如何決定片段可以收到的回調(diào)方法。 例如,當(dāng) Activity 收到其 onCreate() 回調(diào)時(shí),Activity 中的片段只會(huì)收到 onActivityCreated() 回調(diào)。
一旦 Activity 達(dá)到恢復(fù)狀態(tài),您就可以隨意向 Activity 添加片段和移除其中的片段。 因此,只有當(dāng) Activity 處于恢復(fù)狀態(tài)時(shí),片段的生命周期才能獨(dú)立變化。
不過(guò),當(dāng) Activity 離開(kāi)恢復(fù)狀態(tài)時(shí),片段會(huì)在 Activity 的推動(dòng)下再次經(jīng)歷其生命周期。
示例
為了將本文闡述的所有內(nèi)容融會(huì)貫通,以下提供了一個(gè)示例,其中的 Activity 使用兩個(gè)片段來(lái)創(chuàng)建一個(gè)雙窗格布局。 下面的 Activity 包括兩個(gè)片段:一個(gè)用于顯示莎士比亞戲劇標(biāo)題列表,另一個(gè)用于從列表中選定戲劇時(shí)顯示其摘要。 此外,它還展示了如何根據(jù)屏幕配置提供不同的片段配置。
注:FragmentLayout.java
中提供了此 Activity 的完整源代碼。
主 Activity 會(huì)在 onCreate()期間以常規(guī)方式應(yīng)用布局:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_layout);
}
應(yīng)用的布局為 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>
通過(guò)使用此布局,系統(tǒng)可在 Activity 加載布局時(shí)立即實(shí)例化 TitlesFragment
(列出戲劇標(biāo)題),而 FrameLayout(用于顯示戲劇摘要的片段所在位置)則會(huì)占用屏幕右側(cè)的空間,但最初處于空白狀態(tài)。 正如您將在下文所見(jiàn)的那樣,用戶從列表中選擇某個(gè)項(xiàng)目后,系統(tǒng)才會(huì)將片段放入FrameLayout。
不過(guò),并非所有屏幕配置都具有足夠的寬度,可以并排顯示戲劇列表和摘要。 因此,以上布局僅用于橫向屏幕配置(布局保存在 res/layout-land/fragment_layout.xml)。
因此,當(dāng)屏幕縱向顯示時(shí),系統(tǒng)會(huì)應(yīng)用以下布局(保存在 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。這意味著,當(dāng)設(shè)備縱向顯示時(shí),只有戲劇標(biāo)題列表可見(jiàn)。 因此,當(dāng)用戶在此配置中點(diǎn)擊某個(gè)列表項(xiàng)時(shí),應(yīng)用會(huì)啟動(dòng)一個(gè)新 Activity 來(lái)顯示摘要,而不是加載另一個(gè)片段。
接下來(lái),您可以看到如何在片段類中實(shí)現(xiàn)此目的。第一個(gè)片段是 TitlesFragment,它顯示莎士比亞戲劇標(biāo)題列表。該片段擴(kuò)展了 ListFragment,并依靠它來(lái)處理大多數(shù)列表視圖工作。
當(dāng)您檢查此代碼時(shí),請(qǐng)注意,用戶點(diǎn)擊列表項(xiàng)時(shí)可能會(huì)出現(xiàn)兩種行為:系統(tǒng)可能會(huì)創(chuàng)建并顯示一個(gè)新片段,從而在同一 Activity 中顯示詳細(xì)信息(將片段添加到 FrameLayout),也可能會(huì)啟動(dòng)一個(gè)新 Activity(在該 Activity 中可顯示片段),具體取決于這兩個(gè)布局中哪一個(gè)處于活動(dòng)狀態(tài)。
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);
}
}}
第二個(gè)片段 DetailsFragment顯示從 TitlesFragment的列表中選擇的項(xiàng)目的戲劇摘要:
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類中重新調(diào)用,如果用戶點(diǎn)擊某個(gè)列表項(xiàng),且當(dāng)前布局“根本不”**包括 R.id.details視圖(即 DetailsFragment
所屬視圖),則應(yīng)用會(huì)啟動(dòng) DetailsActivity Activity 以顯示該項(xiàng)目的內(nèi)容。
以下是 DetailsActivity,它簡(jiǎn)單地嵌入了 DetailsFragment,以在屏幕為縱向時(shí)顯示所選的戲劇摘要:
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();
}
}}
請(qǐng)注意,如果配置為橫向,則此 Activity 會(huì)自行完成,以便主 Activity 可以接管并沿 TitlesFragment顯示 DetailsFragment。如果用戶在縱向顯示時(shí)啟動(dòng) DetailsActivity,但隨后旋轉(zhuǎn)為橫向(這會(huì)重啟當(dāng)前 Activity),就可能出現(xiàn)這種情況。