Android Jetpack - Fragment官方說明

Android架構發表於2019-01-29

前言


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

fragment必須始終嵌入在 Activity 中,其生命週期直接受宿主 Activity 生命週期的影響。 例如,當 Activity 暫停時,其中的所有fragment也會暫停;當 Activity 被銷燬時,所有fragment也會被銷燬。 不過,當 Activity 正在執行(處於已恢復生命週期狀態)時,您可以獨立操縱每個fragment,如新增或移除它們。 當您執行此類fragment事務時,您也可以將其新增到由 Activity 管理的返回棧 — Activity 中的每個返回棧條目都是一條已發生fragment事務的記錄。 返回棧讓使用者可以通過按返回按鈕撤消fragment事務(後退)。

當您將fragment作為 Activity 佈局的一部分新增時,它存在於 Activity 檢視層次結構的某個 ViewGroup 內部,並且fragment會定義其自己的檢視佈局。您可以通過在 Activity 的佈局檔案中宣告fragment,將其作為 <fragment> 元素插入您的 Activity 佈局中,或者通過將其新增到某個現有 ViewGroup,利用應用程式碼進行插入。不過,fragment並非必須成為 Activity 佈局的一部分;您還可以將沒有自己 UI 的fragment用作 Activity 的不可見工作執行緒。

本文描述如何在開發您的應用時使用fragment,包括將fragment新增到 Activity 返回棧時如何保持其狀態、如何與 Activity 及 Activity 中的其他fragment共享事件、如何為 Activity 的操作欄發揮作用等等。

設計原理


Android 在 Android 3.0(API 級別 11)中引入了fragment,主要是為了給大螢幕(如平板電腦)上更加動態和靈活的 UI 設計提供支援。由於平板電腦的螢幕比手機螢幕大得多,因此可用於組合和交換 UI 元件的空間更大。利用fragment實現此類設計時,您無需管理對檢視層次結構的複雜更改。 通過將 Activity 佈局分成fragment,您可以在執行時修改 Activity 的外觀,並在由 Activity 管理的返回棧中保留這些更改。

例如,新聞應用可以使用一個fragment在左側顯示文章列表,使用另一個fragment在右側顯示文章 — 兩個fragment並排顯示在一個 Activity 中,每個fragment都具有自己的一套生命週期回撥方法,並各自處理自己的使用者輸入事件。 因此,使用者不需要使用一個 Activity 來選擇文章,然後使用另一個 Activity 來閱讀文章,而是可以在同一個 Activity 內選擇文章並進行閱讀,如圖 1 中的平板電腦佈局所示。

您應該將每個fragment都設計為可重複使用的模組化 Activity 元件。也就是說,由於每個fragment都會通過各自的生命週期回撥來定義其自己的佈局和行為,您可以將一個fragment加入多個 Activity,因此,您應該採用可複用式設計,避免直接從某個fragment直接操縱另一個fragment。 這特別重要,因為模組化fragment讓您可以通過更改fragment的組合方式來適應不同的螢幕尺寸。 在設計可同時支援平板電腦和手機的應用時,您可以在不同的佈局配置中重複使用您的fragment,以根據可用的螢幕空間優化使用者體驗。 例如,在手機上,如果不能在同一 Activity 內儲存多個fragment,可能必須利用單獨fragment來實現單窗格 UI。

圖 1

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

例如 ,仍然以新聞應用為例 , 在平板電腦尺寸的裝置上執行時,該應用可以在 Activity A 中嵌入兩個fragment。 不過,在手機尺寸的螢幕上,沒有足以儲存兩個fragment的空間,因此Activity A 只包括用於顯示文章列表的fragment,當使用者選擇文章時,它會啟動Activity B,其中包括用於閱讀文章的第二個fragment。 因此,應用可通過重複使用不同組合的fragment來同時支援平板電腦和手機,如圖 1 所示。

建立fragment


要想建立fragment,您必須建立 Fragment的子類(或已有其子類)。Fragment 類的程式碼與 Activity 非常相似。它包含與 Activity 類似的回撥方法,如 onCreate()onStart()onPause()onStop()。實際上,如果您要將現有 Android 應用轉換為使用fragment,可能只需將程式碼從 Activity 的回撥方法移入fragment相應的回撥方法中。下圖是fragment生命週期方法:

Android Jetpack - Fragment官方說明

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

onCreate() 系統會在建立fragment時呼叫此方法。您應該在實現內初始化您想在fragment暫停或停止後恢復時保留的必需fragment元件。

onCreateView() 系統會在fragment首次繪製其使用者介面時呼叫此方法。 要想為您的fragment繪製 UI,您從此方法中返回的 View必須是fragment佈局的根檢視。如果fragment未提供 UI,您可以返回 null。

onPause() 系統將此方法作為使用者離開fragment的第一個訊號(但並不總是意味著此fragment會被銷燬)進行呼叫。 您通常應該在此方法內確認在當前使用者會話結束後仍然有效的任何更改(因為使用者可能不會返回)。

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

您可能還想擴充套件幾個子類,而不是 Fragment類:

DialogFragment 顯示浮動對話方塊。使用此類建立對話方塊可有效地替代使用 Activity類中的對話方塊幫助程式方法,因為您可以將fragment對話方塊納入由 Activity 管理的fragment返回棧,從而使使用者能夠返回清除的fragment。

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

PreferenceFragment 以列表形式顯示 Preference 物件的層次結構,類似於 PreferenceActivity。這在為您的應用建立“設定” Activity 時很有用處。

新增使用者介面

fragment通常用作 Activity 使用者介面的一部分,將其自己的佈局融入 Activity。

要想為fragment提供佈局,您必須實現 onCreateView() 回撥方法,Android 系統會在fragment需要繪製其佈局時呼叫該方法。您對此方法的實現返回的 View必須是fragment佈局的根檢視。

:如果您的fragment是 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);
    }
}
複製程式碼

建立佈局

在上例中,R.layout.example_fragment 是對應用資源中儲存的名為 example_fragment.xml 的佈局資源的引用。

傳遞至 onCreateView()container 引數是您的fragment佈局將插入到的父 ViewGroup(來自 Activity 的佈局)。savedInstanceState 引數是在恢復fragment時,提供上一fragment例項相關資料的 Bundle處理fragment生命週期部分對恢復狀態做了詳細闡述)。

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

  • 您想要擴充套件的佈局的資源 ID;
  • 將作為擴充套件布局父項的 ViewGroup。傳遞 container 對系統向擴充套件布局的根檢視(由其所屬的父檢視指定)應用佈局引數具有重要意義;
  • 指示是否應該在擴充套件期間將擴充套件布局附加至 ViewGroup(第二個引數)的布林值。(在本例中,其值為 false,因為系統已經將擴充套件布局插入 container — 傳遞 true 值會在最終佈局中建立一個多餘的檢視組。)

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

向 Activity 新增fragment

通常,fragment向宿主 Activity 貢獻一部分 UI,作為 Activity 總體檢視層次結構的一部分嵌入到 Activity 中。可以通過兩種方式向 Activity 佈局新增fragment:

  • 在 Activity 的佈局檔案內宣告fragment

    在本例中,您可以將fragment當作檢視來為其指定佈局屬性。 例如,以下是一個具有兩個fragment的 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 類。

當系統建立此 Activity 佈局時,會例項化在佈局中指定的每個fragment,併為每個fragment呼叫 onCreateView() 方法,以檢索每個fragment的佈局。系統會直接插入fragment返回的View來替代 <fragment> 元素。

:每個fragment都需要一個唯一的識別符號,重啟 Activity 時,系統可以使用該識別符號來恢復fragment(您也可以使用該識別符號來捕獲fragment以執行某些事務,如將其移除)。 可以通過三種方式為fragment提供 ID:

  • android:id 屬性提供唯一 ID。

  • android:tag 屬性提供唯一字串。

  • 如果您未給以上兩個屬性提供值,系統會使用容器檢視的 ID。

  • 或者通過程式碼方式將fragment新增到某個現有 ViewGroup

    您可以在 Activity 執行期間隨時將fragment新增到 Activity 佈局中。您只需指定要將fragment放入哪個 ViewGroup

    要想在您的 Activity 中執行fragment事務(如新增、移除或替換fragment),您必須使用 FragmentTransaction 中的 API。您可以像下面這樣從 Activity 獲取一個 FragmentTransaction 例項:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
複製程式碼

然後,您可以使用 add() 方法新增一個fragment,指定要新增的fragment以及將其插入哪個檢視。例如:

ExampleFragment fragment =  new  ExampleFragment(); 
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
複製程式碼

傳遞到 add() 的第一個引數是 ViewGroup,即應該放置fragment的位置,由資源 ID 指定,第二個引數是要新增的fragment。

一旦您通過 FragmentTransaction 做出了更改,就必須呼叫 commit() 以使更改生效。

新增沒有 UI 的fragment

上例展示瞭如何向您的 Activity 新增fragment以提供 UI。不過,您還可以使用fragment為 Activity 提供後臺行為,而不顯示額外 UI。(glide圖片載入框架就使用了這種方式通過監聽沒有UI的fragment的生命週期來知道Activity的生命週期。)

要想新增沒有 UI 的fragment,請使用 add(Fragment, String) 從 Activity 新增fragment(為fragment提供一個唯一的字串“標記”,而不是檢視 ID)。 這會新增fragment,但由於它並不與 Activity 佈局中的檢視關聯,因此不會收到對onCreateView() 的呼叫。因此,您不需要實現該方法。

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

管理fragment


要想管理您的 Activity 中的fragment,您需要使用 FragmentManager。要想獲取它,請從您的 Activity 呼叫getFragmentManager()

您可以使用 FragmentManager 執行的操作包括:

  • 通過 findFragmentById()(對於在 Activity 佈局中提供 UI 的fragment)或 findFragmentByTag()(對於提供或不提供 UI 的fragment)獲取 Activity 中存在的fragment。
  • 通過 popBackStack()(模擬使用者發出的返回命令)將fragment從返回棧中彈出。
  • 通過 addOnBackStackChangedListener() 註冊一個偵聽返回棧變化的偵聽器。

如需瞭解有關這些方法以及其他方法的詳細資訊,請參閱 FragmentManager 類文件。

如上文所示,您也可以使用 FragmentManager 開啟一個 FragmentTransaction,通過它來執行某些事務,如新增和移除fragment。

執行fragment事務


在 Activity 中使用fragment的一大優點是,可以根據使用者行為通過它們執行新增、移除、替換以及其他操作。 您提交給 Activity 的每組更改都稱為事務,您可以使用 [FragmentTransaction] 中的 API 來執行一項事務。您也可以將每個事務儲存到由 Activity 管理的返回棧內,從而讓使用者能夠回退fragment更改(類似於回退 Activity)。

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

FragmentManager fragmentManager =  getFragmentManager();  
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
複製程式碼

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

不過,在您呼叫 commit() 之前,您可能想呼叫 addToBackStack(),以將事務新增到fragment事務返回棧。 該返回棧由 Activity 管理,允許使用者通過按返回按鈕返回上一fragment狀態。

例如,以下示例說明了如何將一個fragment替換成另一個fragment,以及如何在返回棧中保留先前狀態:

// 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 所標識的佈局容器中的任何fragment(如有)。通過呼叫 addToBackStack() 可將替換事務儲存到返回棧,以便使用者能夠通過按返回按鈕撤消事務並回退到上一fragment。

如果您向事務新增了多個更改(如又一個 add()remove()),並且呼叫了 addToBackStack(),則在呼叫commit() 前應用的所有更改都將作為單一事務新增到返回棧,並且返回按鈕會將它們一併撤消。

FragmentTransaction 新增更改的順序無關緊要,不過:

  • 您必須最後呼叫 commit()
  • 如果您要向同一容器新增多個fragment,則您新增fragment的順序將決定它們在檢視層次結構中的出現順序

如果您沒有在執行移除fragment的事務時呼叫 addToBackStack(),則事務提交時該fragment會被銷燬,使用者將無法回退到該fragment。 不過,如果您在刪除fragment時呼叫了 addToBackStack(),則系統會停止該fragment,並在使用者回退時將其恢復。

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

呼叫 commit() 不會立即執行事務,而是在 Activity 的 UI 執行緒(“主”執行緒)可以執行該操作時再安排其線上程上執行。不過,如有必要,您也可以從 UI 執行緒呼叫 executePendingTransactions() 以立即執行 commit() 提交的事務。通常不必這樣做,除非其他執行緒中的作業依賴該事務。

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

與 Activity 通訊


儘管 Fragment 是作為獨立於 Activity 的物件實現,並且可在多個 Activity 內使用,但fragment的給定例項會直接繫結到包含它的 Activity。

具體地說,fragment可以通過 getActivity() 訪問 Activity 例項,並輕鬆地執行在 Activity 佈局中查詢檢視等任務。

View listView =  getActivity().findViewById(R.id.list);
複製程式碼

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

ExampleFragment fragment =  
(ExampleFragment)getFragmentManager().findFragmentById(R.id.example_fragment);
複製程式碼

建立對 Activity 的事件回撥

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

例如,如果一個新聞應用的 Activity 有兩個fragment — 一個用於顯示文章列表(fragment A),另一個用於顯示文章(fragment B)— 那麼fragment A 必須在列表項被選定後告知 Activity,以便它告知fragment B 顯示該文章。 在本例中,OnArticleSelectedListener 介面在fragment A 內宣告:

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

然後,該fragment的宿主 Activity 會實現 OnArticleSelectedListener 介面並替代 onArticleSelected(),將來自fragment A 的事件通知fragment B。為確保宿主 Activity 實現此介面,fragment A 的 onAttach() 回撥方法(系統在向 Activity 新增fragment時呼叫的方法)會通過轉換傳遞到 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 未實現介面,則fragment會引發 ClassCastException。實現時,mListener 成員會保留對 Activity 的 OnArticleSelectedListener 實現的引用,以便fragment A 可以通過呼叫 OnArticleSelectedListener 介面定義的方法與 Activity 共享事件。例如,如果fragment A 是 ListFragment 的一個擴充套件,則使用者每次點選列表項時,系統都會呼叫fragment中的 onListItemClick(),然後該方法會呼叫 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 引數是被點選項的行 ID,即 Activity(或其他fragment)用來從應用的 ContentProvider 獲取文章的 ID。

嚮應用欄新增專案

您的fragment可以通過實現 onCreateOptionsMenu() 向 Activity 的選項選單(並因此嚮應用欄)貢獻選單項。不過,為了使此方法能夠收到呼叫,您必須在 onCreate() 期間呼叫 setHasOptionsMenu(),以指示fragment想要向選項選單新增選單項(否則,fragment將不會收到對 onCreateOptionsMenu() 的呼叫)。

您之後從fragment新增到選項選單的任何選單項都將追加到現有選單項之後。 選定選單項時,fragment還會收到對 onOptionsItemSelected() 的回撥。

您還可以通過呼叫 registerForContextMenu(),在fragment佈局中註冊一個檢視來提供上下文選單。使用者開啟上下文選單時,fragment會收到對 onCreateContextMenu() 的呼叫。當使用者選擇某個選單項時,fragment會收到對 onContextItemSelected() 的呼叫。

:儘管您的fragment會收到與其新增的每個選單項對應的選單項選定回撥,但當使用者選擇選單項時,Activity 會首先收到相應的回撥。 如果 Activity 對選單項選定回撥的實現不會處理選定的選單項,則系統會將事件傳遞到fragment的回撥。 這適用於選項選單和上下文選單。

處理fragment生命週期


圖 3

Activity 生命週期對fragment生命週期的影響。

管理fragment生命週期與管理 Activity 生命週期很相似。和 Activity 一樣,fragment也以三種狀態存在:

繼續 fragment在執行中的 Activity 中可見。

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

停止 fragment不可見。宿主 Activity 已停止,或fragment已從 Activity 中移除,但已新增到返回棧。 停止fragment仍然處於活動狀態(系統會保留所有狀態和成員資訊)。 不過,它對使用者不再可見,如果 Activity 被終止,它也會被終止。

同樣與 Activity 一樣,假使 Activity 的程式被終止,而您需要在重建 Activity 時恢復fragment狀態,您也可以使用 Bundle保留fragment的狀態。您可以在fragment的 onSaveInstanceState() 回撥期間儲存狀態,並可在onCreate()onCreateView()onActivityCreated() 期間恢復狀態。

Activity 生命週期與fragment生命週期之間的最顯著差異在於它們在其各自返回棧中的儲存方式。 預設情況下,Activity 停止時會被放入由系統管理的 Activity 返回棧(以便使用者通過返回按鈕回退到 Activity)。不過,僅當您在移除fragment的事務執行期間通過呼叫addToBackStack() 顯式請求儲存例項時,系統才會將fragment放入由宿主 Activity 管理的返回棧。

在其他方面,管理fragment生命週期與管理 Activity 生命週期非常相似。 因此,管理 Activity 生命週期的做法同樣適用於fragment。 但您還需要了解 Activity 的生命週期對fragment生命週期的影響。

注意:如需 Fragment 內的某個 Context 物件,可以呼叫 getActivity()。但要注意,請僅在fragment附加到 Activity 時呼叫 getActivity()。如果fragment尚未附加,或在其生命週期結束期間分離,則 getActivity() 將返回 null。

與 Activity 生命週期協調一致

fragment所在的 Activity 的生命週期會直接影響fragment的生命週期,其表現為,Activity 的每次生命週期回撥都會引發每個fragment的類似回撥。 例如,當 Activity 收到 onPause() 時,Activity 中的每個fragment也會收到 onPause()

不過,fragment還有幾個額外的生命週期回撥,用於處理與 Activity 的唯一互動,以執行構建和銷燬fragment UI 等操作。 這些額外的回撥方法是:

onAttach() 在fragment已與 Activity 關聯時呼叫(Activity 傳遞到此方法內)。

onCreateView() 呼叫它可建立與fragment關聯的檢視層次結構。

onActivityCreated() 在 Activity 的 onCreate() 方法已返回時呼叫。

onDestroyView() 在移除與fragment關聯的檢視層次結構時呼叫。

onDetach() 在取消fragment與 Activity 的關聯時呼叫。

圖 3 圖示說明了受其宿主 Activity 影響的fragment生命週期流。在該圖中,您可以看到 Activity 的每個連續狀態如何決定fragment可以收到的回撥方法。 例如,當 Activity 收到其 onCreate() 回撥時,Activity 中的fragment只會收到onActivityCreated() 回撥。

一旦 Activity 達到恢復狀態,您就可以隨意向 Activity 新增fragment和移除其中的fragment。 因此,只有當 Activity 處於恢復狀態時,fragment的生命週期才能獨立變化。

不過,當 Activity 離開恢復狀態時,fragment會在 Activity 的推動下再次經歷其生命週期。

相關文章