Fragment

Fragment 表示 Activity中的行為或用戶界面部分。您可以將多個片段組合在一個 Activity 中來構建多窗格 UI绩蜻,以及在多個 Activity 中重復使用某個片段降淮。您可以將片段視為 Activity 的模塊化組成部分穿挨,它具有自己的生命周期但壮,能接收自己的輸入事件焦辅,并且您可以在 Activity 運行時添加或移除片段(有點像您可以在不同 Activity 中重復使用的“子 Activity”)苗傅。

片段必須始終嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影響。 例如,當 Activity 暫停時,其中的所有片段也會暫停岖食;當 Activity 被銷毀時捐腿,所有片段也會被銷毀耀找。 不過,當 Activity 正在運行(處于已恢復生命周期狀態(tài))時,您可以獨立操縱每個片段茅糜,如添加或移除它們耙箍。 當您執(zhí)行此類片段事務時尉辑,您也可以將其添加到由 Activity 管理的返回棧 — Activity 中的每個返回棧條目都是一條已發(fā)生片段事務的記錄。 返回棧讓用戶可以通過按返回按鈕撤消片段事務(后退)解孙。

當您將片段作為 Activity 布局的一部分添加時坑填,它存在于 Activity 視圖層次結構的某個 ViewGroup 內部,并且片段會定義其自己的視圖布局妆距。您可以通過在 Activity 的布局文件中聲明片段穷遂,將其作為 <fragment> 元素插入您的 Activity 布局中,或者通過將其添加到某個現有ViewGroup利用應用代碼進行插入娱据。不過蚪黑,片段并非必須成為 Activity 布局的一部分;您還可以將沒有自己 UI 的片段用作 Activity 的不可見工作線程中剩。

本文描述如何在開發(fā)您的應用時使用片段忌穿,包括將片段添加到 Activity 返回棧時如何保持其狀態(tài)、如何與 Activity 及 Activity 中的其他片段共享事件结啼、如何為 Activity 的操作欄發(fā)揮作用等等掠剑。

一、設計原理

Android 在 Android 3.0(API 級別 11)中引入了片段郊愧,主要是為了給大屏幕(如平板電腦)上更加動態(tài)和靈活的 UI 設計提供支持朴译。由于平板電腦的屏幕比手機屏幕大得多,因此可用于組合和交換 UI 組件的空間更大属铁。利用片段實現此類設計時眠寿,您無需管理對視圖層次結構的復雜更改。 通過將 Activity 布局分成片段焦蘑,您可以在運行時修改 Activity 的外觀盯拱,并在由 Activity 管理的返回棧中保留這些更改。

例如,新聞應用可以使用一個片段在左側顯示文章列表狡逢,使用另一個片段在右側顯示文章 — 兩個片段并排顯示在一個 Activity 中宁舰,每個片段都具有自己的一套生命周期回調方法,并各自處理自己的用戶輸入事件奢浑。 因此蛮艰,用戶不需要使用一個 Activity 來選擇文章,然后使用另一個 Activity 來閱讀文章殷费,而是可以在同一個 Activity 內選擇文章并進行閱讀印荔,如圖 1 中的平板電腦布局所示。

您應該將每個片段都設計為可重復使用的模塊化 Activity 組件详羡。也就是說仍律,由于每個片段都會通過各自的生命周期回調來定義其自己的布局和行為,您可以將一個片段加入多個 Activity实柠,因此水泉,您應該采用可復用式設計,避免直接從某個片段直接操縱另一個片段窒盐。 這特別重要草则,因為模塊化片段讓您可以通過更改片段的組合方式來適應不同的屏幕尺寸。 在設計可同時支持平板電腦和手機的應用時蟹漓,您可以在不同的布局配置中重復使用您的片段炕横,以根據可用的屏幕空間優(yōu)化用戶體驗。 例如葡粒,在手機上份殿,如果不能在同一 Activity 內儲存多個片段,可能必須利用單獨片段來實現單窗格 UI嗽交。

image

圖 1. 有關由片段定義的兩個 UI 模塊如何適應不同設計的示例:通過組合成一個 Activity 來適應平板電腦設計卿嘲,通過單獨片段來適應手機設計。

例如 — 仍然以新聞應用為例 — 在平板電腦尺寸的設備上運行時夫壁,該應用可以在 Activity A 中嵌入兩個片段拾枣。 不過,在手機尺寸的屏幕上盒让,沒有足以儲存兩個片段的空間梅肤,因此Activity A 只包括用于顯示文章列表的片段,當用戶選擇文章時邑茄,它會啟動Activity B凭语,其中包括用于閱讀文章的第二個片段。 因此撩扒,應用可通過重復使用不同組合的片段來同時支持平板電腦和手機,如圖 1 所示。

如需了解有關通過利用不同片段組合來適應不同屏幕配置這種方法設計應用的詳細信息搓谆,請參閱支持平板電腦和手機指南炒辉。

二、創(chuàng)建片段

image

圖 2. 片段的生命周期(其 Activity 運行時)泉手。

要想創(chuàng)建片段黔寇,您必須創(chuàng)建 Fragment 的子類(或已有其子類)。Fragment 類的代碼與 Activity非常相似斩萌。它包含與 Activity 類似的回調方法缝裤,如 onCreate()onStart()颊郎、onPause()onStop()憋飞。實際上,如果您要將現有 Android 應用轉換為使用片段姆吭,可能只需將代碼從 Activity 的回調方法移入片段相應的回調方法中榛做。

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

onCreate()
系統(tǒng)會在創(chuàng)建片段時調用此方法内狸。您應該在實現內初始化您想在片段暫图烀校或停止后恢復時保留的必需片段組件。

onCreateView()
系統(tǒng)會在片段首次繪制其用戶界面時調用此方法昆淡。 要想為您的片段繪制 UI锰瘸,您從此方法中返回的 View 必須是片段布局的根視圖。如果片段未提供 UI昂灵,您可以返回 null避凝。

onPause()
系統(tǒng)將此方法作為用戶離開片段的第一個信號(但并不總是意味著此片段會被銷毀)進行調用。 您通常應該在此方法內確認在當前用戶會話結束后仍然有效的任何更改(因為用戶可能不會返回)倔既。

大多數應用都應該至少為每個片段實現這三個方法恕曲,但您還應該使用幾種其他回調方法來處理片段生命周期的各個階段。 處理片段生命周期部分對所有生命周期回調方法做了更詳盡的闡述渤涌。

您可能還想擴展幾個子類佩谣,而不是 Fragment基類:

DialogFragment
顯示浮動對話框。使用此類創(chuàng)建對話框可有效地替代使用Activity類中的對話框幫助程序方法实蓬,因為您可以將片段對話框納入由 Activity 管理的片段返回棧茸俭,從而使用戶能夠返回清除的片段。

ListFragment
顯示由適配器(如 SimpleCursorAdapter)管理的一系列項目安皱,類似于ListActivity它提供了幾種管理列表視圖的方法调鬓,如用于處理點擊事件的 onListItemClick()回調。

PreferenceFragment
以列表形式顯示 Preference對象的層次結構酌伊,類似于 PreferenceActivity腾窝。這在為您的應用創(chuàng)建“設置” Activity 時很有用處缀踪。

1、添加用戶界面

片段通常用作 Activity 用戶界面的一部分虹脯,將其自己的布局融入 Activity驴娃。

要想為片段提供布局,您必須實現 onCreateView() 回調方法循集,Android 系統(tǒng)會在片段需要繪制其布局時調用該方法唇敞。您對此方法的實現返回的 View 必須是片段布局的根視圖。

:如果您的片段是 ListFragment的子類咒彤,則默認實現會從onCreateView()返回一個ListView疆柔,因此您無需實現它。

要想從onCreateView()返回布局镶柱,您可以通過 XML 中定義的布局資源來擴展布局旷档。為幫助您執(zhí)行此操作,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);
    }
}

2、創(chuàng)建布局

在上例中查吊,R.layout.example_fragment 是對應用資源中保存的名為 example_fragment.xml 的布局資源的引用谐区。如需了解有關如何在 XML 中創(chuàng)建布局的信息,請參閱用戶界面文檔逻卖。

傳遞至 onCreateView()的 container 參數是您的片段布局將插入到的父 ViewGroup(來自 Activity 的布局)宋列。savedInstanceState 參數是在恢復片段時,提供上一片段實例相關數據的Bundle(處理片段生命周期部分對恢復狀態(tài)做了詳細闡述)评也。

inflate() 方法帶有三個參數:

  • 您想要擴展的布局的資源 ID炼杖;
  • 將作為擴展布局父項的 ViewGroup。傳遞 container 對系統(tǒng)向擴展布局的根視圖(由其所屬的父視圖指定)應用布局參數具有重要意義盗迟;
  • 指示是否應該在擴展期間將擴展布局附加至 ViewGroup(第二個參數)的布爾值坤邪。(在本例中,其值為 false罚缕,因為系統(tǒng)已經將擴展布局插入 container — 傳遞 true 值會在最終布局中創(chuàng)建一個多余的視圖組艇纺。)

現在,您已經了解了如何創(chuàng)建提供布局的片段邮弹。接下來黔衡,您需要將該片段添加到您的 Activity 中。

3腌乡、向 Activity 添加片段

通常盟劫,片段向宿主 Activity 貢獻一部分 UI,作為 Activity 總體視圖層次結構的一部分嵌入到 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 類氢哮。

當系統(tǒng)創(chuàng)建此 Activity 布局時,會實例化在布局中指定的每個片段型檀,并為每個片段調用 onCreateView() 方法,以檢索每個片段的布局听盖。系統(tǒng)會直接插入片段返回的 View 來替代 <fragment> 元素胀溺。

注:每個片段都需要一個唯一的標識符,重啟 Activity 時皆看,系統(tǒng)可以使用該標識符來恢復片段(您也可以使用該標識符來捕獲片段以執(zhí)行某些事務仓坞,如將其移除)。 可以通過三種方式為片段提供 ID:

  • android:id 屬性提供唯一 ID腰吟。
  • android:tag 屬性提供唯一字符串无埃。
  • 如果您未給以上兩個屬性提供值,系統(tǒng)會使用容器視圖的 ID毛雇。
  • 或者通過編程方式將片段添加到某個現有 ViewGroup

    您可以在 Activity 運行期間隨時將片段添加到 Activity 布局中嫉称。您只需指定要將片段放入哪個 ViewGroup

    要想在您的 Activity 中執(zhí)行片段事務(如添加灵疮、移除或替換片段)织阅,您必須使用 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 的片段

上例展示了如何向您的 Activity 添加片段以提供 UI晾腔。不過舌稀,您還可以使用片段為 Activity 提供后臺行為,而不顯示額外 UI灼擂。

要想添加沒有 UI 的片段壁查,請使用 add(Fragment, String) 從 Activity 添加片段(為片段提供一個唯一的字符串“標記”,而不是視圖 ID)剔应。 這會添加片段睡腿,但由于它并不與 Activity 布局中的視圖關聯语御,因此不會收到對 onCreateView() 的調用。因此席怪,您不需要實現該方法应闯。

并非只能為非 UI 片段提供字符串標記 — 您也可以為具有 UI 的片段提供字符串標記 — 但如果片段沒有 UI,則字符串標記將是標識它的唯一方式挂捻。如果您想稍后從 Activity 中獲取片段碉纺,則需要使用 findFragmentByTag()

如需查看將沒有 UI 的片段用作后臺工作線程的示例 Activity刻撒,請參閱 FragmentRetainInstance.java 示例骨田,該示例包括在 SDK 示例(通過 Android SDK 管理器提供)中,以<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java 形式位于您的系統(tǒng)中声怔。

三态贤、管理片段

要想管理您的 Activity 中的片段,您需要使用 FragmentManager醋火。要想獲取它悠汽,請從您的 Activity 調用getFragmentManager()

您可以使用 FragmentManager 執(zhí)行的操作包括:

  • 通過 findFragmentById()(對于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(對于提供或不提供 UI 的片段)獲取 Activity 中存在的片段芥驳。
  • 通過 popBackStack()(模擬用戶發(fā)出的返回命令)將片段從返回棧中彈出柿冲。
  • 通過 addOnBackStackChangedListener() 注冊一個偵聽返回棧變化的偵聽器。

如需了解有關這些方法以及其他方法的詳細信息晚树,請參閱 FragmentManager 類文檔姻采。

如上文所示,您也可以使用 FragmentManager 打開一個 FragmentTransaction爵憎,通過它來執(zhí)行某些事務慨亲,如添加和移除片段。

四宝鼓、執(zhí)行片段事務

在 Activity 中使用片段的一大優(yōu)點是刑棵,可以根據用戶行為通過它們執(zhí)行添加、移除愚铡、替換以及其他操作蛉签。 您提交給 Activity 的每組更改都稱為事務,您可以使用 FragmentTransaction 中的 API 來執(zhí)行一項事務沥寥。您也可以將每個事務保存到由 Activity 管理的返回棧內碍舍,從而讓用戶能夠回退片段更改(類似于回退 Activity)。

您可以像下面這樣從 FragmentManager 獲取一個 FragmentTransaction 實例:

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

每個事務都是您想要同時執(zhí)行的一組更改邑雅。您可以使用 add()片橡、remove()replace() 等方法為給定事務設置您想要執(zhí)行的所有更改。然后淮野,要想將事務應用到 Activity捧书,您必須調用 commit()吹泡。

不過,在您調用 commit() 之前经瓷,您可能想調用 addToBackStack()爆哑,以將事務添加到片段事務返回棧。 該返回棧由 Activity 管理舆吮,允許用戶通過按返回按鈕返回上一片段狀態(tài)揭朝。

例如,以下示例說明了如何將一個片段替換成另一個片段色冀,以及如何在返回棧中保留先前狀態(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 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()前應用的所有更改都將作為單一事務添加到返回棧,并且返回按鈕會將它們一并撤消瘪吏。

FragmentTransaction 添加更改的順序無關緊要癣防,不過:

  • 您必須最后調用 commit()
  • 如果您要向同一容器添加多個片段,則您添加片段的順序將決定它們在視圖層次結構中的出現順序

如果您沒有在執(zhí)行移除片段的事務時調用 addToBackStack()掌眠,則事務提交時該片段會被銷毀蕾盯,用戶將無法回退到該片段。 不過蓝丙,如果您在刪除片段時調用了 addToBackStack()级遭,則系統(tǒng)會停止該片段,并在用戶回退時將其恢復渺尘。

提示:對于每個片段事務挫鸽,您都可以通過在提交前調用 setTransition() 來應用過渡動畫。

調用 commit() 不會立即執(zhí)行事務鸥跟,而是在 Activity 的 UI 線程(“主”線程)可以執(zhí)行該操作時再安排其在線程上運行丢郊。不過,如有必要医咨,您也可以從 UI 線程調用 executePendingTransactions() 以立即執(zhí)行 commit() 提交的事務枫匾。通常不必這樣做,除非其他線程中的作業(yè)依賴該事務拟淮。

注意:您只能在 Activity 保存其狀態(tài)(用戶離開 Activity)之前使用 commit() 提交事務干茉。如果您試圖在該時間點后提交,則會引發(fā)異常惩歉。 這是因為如需恢復 Activity等脂,則提交后的狀態(tài)可能會丟失俏蛮。 對于丟失提交無關緊要的情況,請使用 commitAllowingStateLoss()上遥。

五铭段、與 Activity 通信

盡管 Fragment 是作為獨立于 Activity 的對象實現屡久,并且可在多個 Activity 內使用,但片段的給定實例會直接綁定到包含它的 Activity。

具體地說熏迹,片段可以通過 getActivity() 訪問 Activity 實例,并輕松地執(zhí)行在 Activity 布局中查找視圖等任務雁仲。

ViewlistView = getActivity().findViewById(R.id.list);

同樣地厨钻,您的 Activity 也可以使用 findFragmentById()findFragmentByTag(),通過從 FragmentManager 獲取對 Fragment 的引用來調用片段中的方法燃异。例如:

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

1携狭、創(chuàng)建對 Activity 的事件回調

在某些情況下,您可能需要通過片段與 Activity 共享事件回俐。執(zhí)行此操作的一個好方法是逛腿,在片段內定義一個回調接口,并要求宿主 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() 回調方法(系統(tǒng)在向 Activity 添加片段時調用的方法)會通過轉換傳遞到 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 未實現接口苦锨,則片段會引發(fā) ClassCastException逼泣。實現時,mListener 成員會保留對 Activity 的 OnArticleSelectedListener 實現的引用舟舒,以便片段 A 可以通過調用 OnArticleSelectedListener 接口定義的方法與 Activity 共享事件拉庶。例如,如果片段 A 是 ListFragment 的一個擴展秃励,則用戶每次點擊列表項時氏仗,系統(tǒng)都會調用片段中的 onListItemClick(),然后該方法會調用 onArticleSelected() 以與 Activity 共享事件:

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

傳遞到 onListItemClick()id 參數是被點擊項的行 ID,即 Activity(或其他片段)用來從應用的 ContentProvider獲取文章的 ID皆尔。

內容提供程序文檔中提供了有關內容提供程序用法的更多詳情呐舔。

2、向應用欄添加項目

您的片段可以通過實現 onCreateOptionsMenu() 向 Activity 的選項菜單(并因此向應用欄)貢獻菜單項慷蠕。不過珊拼,為了使此方法能夠收到調用,您必須在 onCreate() 期間調用 setHasOptionsMenu()流炕,以指示片段想要向選項菜單添加菜單項(否則澎现,片段將不會收到對 onCreateOptionsMenu() 的調用)。

您之后從片段添加到選項菜單的任何菜單項都將追加到現有菜單項之后每辟。 選定菜單項時剑辫,片段還會收到對 onOptionsItemSelected() 的回調。

您還可以通過調用 registerForContextMenu()渠欺,在片段布局中注冊一個視圖來提供上下文菜單妹蔽。用戶打開上下文菜單時,片段會收到對 onCreateContextMenu() 的調用挠将。當用戶選擇某個菜單項時讹开,片段會收到對 onContextItemSelected() 的調用。

:盡管您的片段會收到與其添加的每個菜單項對應的菜單項選定回調捐名,但當用戶選擇菜單項時,Activity 會首先收到相應的回調闹击。 如果 Activity 對菜單項選定回調的實現不會處理選定的菜單項镶蹋,則系統(tǒng)會將事件傳遞到片段的回調。 這適用于選項菜單和上下文菜單赏半。

如需了解有關菜單的詳細信息贺归,請參閱菜單開發(fā)者指南和應用欄培訓課程。

六断箫、處理片段生命周期

image

圖 3. Activity 生命周期對片段生命周期的影響拂酣。

管理片段生命周期與管理 Activity 生命周期很相似。和 Activity 一樣仲义,片段也以三種狀態(tài)存在:

繼續(xù)
片段在運行中的 Activity 中可見婶熬。

暫停
另一個 Activity 位于前臺并具有焦點,但此片段所在的 Activity 仍然可見(前臺 Activity 部分透明埃撵,或未覆蓋整個屏幕)>赵颅。

停止
片段不可見。宿主 Activity 已停止暂刘,或片段已從 Activity 中移除饺谬,但已添加到返回棧。 停止片段仍然處于活動狀態(tài)(系統(tǒng)會保留所有狀態(tài)和成員信息)谣拣。 不過募寨,它對用戶不再可見族展,如果 Activity 被終止,它也會被終止拔鹰。

同樣與 Activity 一樣仪缸,假使 Activity 的進程被終止,而您需要在重建 Activity 時恢復片段狀態(tài)格郁,您也可以使用 Bundle 保留片段的狀態(tài)腹殿。您可以在片段的 onSaveInstanceState() 回調期間保存狀態(tài),并可在 onCreate()例书、onCreateView()onActivityCreated() 期間恢復狀態(tài)锣尉。如需了解有關保存狀態(tài)的詳細信息,請參閱 Activity 文檔决采。

Activity 生命周期與片段生命周期之間的最顯著差異在于它們在其各自返回棧中的存儲方式自沧。 默認情況下,Activity 停止時會被放入由系統(tǒng)管理的 Activity 返回棧(以便用戶通過返回按鈕回退到 Activity树瞭,任務和返回棧對此做了闡述)拇厢。不過,僅當您在移除片段的事務執(zhí)行期間通過調用 addToBackStack() 顯式請求保存實例時晒喷,系統(tǒng)才會將片段放入由宿主 Activity 管理的返回棧孝偎。

在其他方面,管理片段生命周期與管理 Activity 生命周期非常相似凉敲。 因此衣盾,管理 Activity 生命周期的做法同樣適用于片段。 但您還需要了解 Activity 的生命周期對片段生命周期的影響爷抓。

注意:如需 Fragment 內的某個 Context 對象势决,可以調用 [getActivity()。但要注意蓝撇,請僅在片段附加到 Activity 時調用getActivity()果复。如果片段尚未附加,或在其生命周期結束期間分離渤昌,則getActivity()` 將返回 null虽抄。

1、與 Activity 生命周期協(xié)調一致

片段所在的 Activity 的生命周期會直接影響片段的生命周期独柑,其表現為极颓,Activity 的每次生命周期回調都會引發(fā)每個片段的類似回調。 例如群嗤,當 Activity 收到 onPause() 時菠隆,Activity 中的每個片段也會收到 onPause()

不過,片段還有幾個額外的生命周期回調骇径,用于處理與 Activity 的唯一交互躯肌,以執(zhí)行構建和銷毀片段 UI 等操作。 這些額外的回調方法是:

onAttach()
在片段已與 Activity 關聯時調用(Activity 傳遞到此方法內)破衔。

onCreateView()
調用它可創(chuàng)建與片段關聯的視圖層次結構清女。

onActivityCreated()
在 Activity 的 onCreate() 方法已返回時調用。

[onDestroyView()
在移除與片段關聯的視圖層次結構時調用晰筛。

onDetach()
在取消片段與 Activity 的關聯時調用嫡丙。

圖 3 圖示說明了受其宿主 Activity 影響的片段生命周期流。在該圖中读第,您可以看到 Activity 的每個連續(xù)狀態(tài)如何決定片段可以收到的回調方法曙博。 例如,當 Activity 收到其 onCreate() 回調時怜瞒,Activity 中的片段只會收到 onActivityCreated()回調父泳。

一旦 Activity 達到恢復狀態(tài),您就可以隨意向 Activity 添加片段和移除其中的片段吴汪。 因此惠窄,只有當 Activity 處于恢復狀態(tài)時,片段的生命周期才能獨立變化漾橙。

不過杆融,當 Activity 離開恢復狀態(tài)時,片段會在 Activity 的推動下再次經歷其生命周期霜运。

七擒贸、示例

為了將本文闡述的所有內容融會貫通,以下提供了一個示例觉渴,其中的 Activity 使用兩個片段來創(chuàng)建一個雙窗格布局徽惋。 下面的 Activity 包括兩個片段:一個用于顯示莎士比亞戲劇標題列表,另一個用于從列表中選定戲劇時顯示其摘要踢京。 此外,它還展示了如何根據屏幕配置提供不同的片段配置宦棺。

FragmentLayout.java 中提供了此 Activity 的完整源代碼。

主 Activity 會在 onCreate() 期間以常規(guī)方式應用布局:

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

通過使用此布局蹈丸,系統(tǒng)可在 Activity 加載布局時立即實例化 TitlesFragment(列出戲劇標題),而 FrameLayout(用于顯示戲劇摘要的片段所在位置)則會占用屏幕右側的空間,但最初處于空白狀態(tài)逻杖。 正如您將在下文所見的那樣,用戶從列表中選擇某個項目后闻伶,系統(tǒng)才會將片段放入 FrameLayout够话。

不過女嘲,并非所有屏幕配置都具有足夠的寬度,可以并排顯示戲劇列表和摘要漂坏。 因此媒至,以上布局僅用于橫向屏幕配置(布局保存在 res/layout-land/fragment_layout.xml)拒啰。

因此,當屏幕縱向顯示時剩失,系統(tǒ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拴孤。這意味著甲捏,當設備縱向顯示時演熟,只有戲劇標題列表可見芒粹。 因此大溜,當用戶在此配置中點擊某個列表項時钦奋,應用會啟動一個新 Activity 來顯示摘要疙赠,而不是加載另一個片段棺聊。

接下來贞谓,您可以看到如何在片段類中實現此目的裸弦。第一個片段是 TitlesFragment理疙,它顯示莎士比亞戲劇標題列表。該片段擴展了 ListFragment砖顷,并依靠它來處理大多數列表視圖工作滤蝠。

當您檢查此代碼時授嘀,請注意蹄皱,用戶點擊列表項時可能會出現兩種行為:系統(tǒng)可能會創(chuàng)建并顯示一個新片段,從而在同一 Activity 中顯示詳細信息(將片段添加到 FrameLayout)压鉴,也可能會啟動一個新 Activity(在該 Activity 中可顯示片段)油吭,具體取決于這兩個布局中哪一個處于活動狀態(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);
        }
    }
}

第二個片段 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 Activity 以顯示該項目的內容珊随。

以下是 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满着,但隨后旋轉為橫向(這會重啟當前 Activity)风喇,就可能出現這種情況。

如需查看使用片段的更多示例(以及本示例的完整源文件)还蹲,請參閱 ApiDemos(可從示例 SDK 組件下載)中提供的 API Demos 示例應用谜喊。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末斗遏,一起剝皮案震驚了整個濱河市鞋邑,隨后出現的幾起案子,更是在濱河造成了極大的恐慌铸本,老刑警劉巖遵堵,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鄙早,死亡現場離奇詭異限番,居然都是意外死亡,警方通過查閱死者的電腦和手機扩灯,發(fā)現死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門珠插,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捻撑,“玉大人缤底,你說我怎么就攤上這事个唧。” “怎么了犁河?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵桨螺,是天一觀的道長灭翔。 經常有香客問我允扇,道長考润,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮井辜,結果婚禮上粥脚,老公的妹妹穿的比我還像新娘刷允。我一直安慰自己,他們只是感情好纤怒,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布泊窘。 她就那樣靜靜地躺著烘豹,像睡著了一般萝映。 火紅的嫁衣襯著肌膚如雪序臂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音构订,去河邊找鬼悼瘾。 笑死,一個胖子當著我的面吹牛卸勺,可吹牛的內容都是我干的曙求。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼苹享!你這毒婦竟也來了浴麻?” 一聲冷哼從身側響起白胀,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤或杠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后认境,有當地人在樹林里發(fā)現了一具尸體叉信,經...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡硼身,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年佳遂,在試婚紗的時候發(fā)現自己被綠了丑罪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡跪另,死狀恐怖罚斗,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情袱吆,我是刑警寧澤绞绒,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布蓬衡,位于F島的核電站,受9級特大地震影響筒饰,放射性物質發(fā)生泄漏瓷们。R本人自食惡果不足惜秒咐,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一攒钳、第九天 我趴在偏房一處隱蔽的房頂上張望雷滋。 院中可真熱鬧晤斩,春花似錦、人聲如沸揩页。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至忍法,卻和暖如春饿序,著一層夾襖步出監(jiān)牢的瞬間羹蚣,已是汗流浹背顽素。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工胁出, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留全蝶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓嫂用,卻偏偏與公主長得像嘱函,于是被迫代替她去往敵國和親往弓。 傳聞我的和親對象是個殘疾皇子蓄氧,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容