Android Jetpack - Fragment官方說明

前言


Fragment 表示 Activity 中的行爲或用戶界面部分。您能夠將多個fragment組合在一個 Activity 中來構建多窗格 UI,以及在多個 Activity 中重複使用某個fragment。您能夠將fragment視爲 Activity 的模塊化組成部分,它具備本身的生命週期,能接收本身的輸入事件,而且您能夠在 Activity 運行時添加或移除fragment(有點像您能夠在不一樣 Activity 中重複使用的「子 Activity」)。html

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

當您將fragment做爲 Activity 佈局的一部分添加時,它存在於 Activity 視圖層次結構的某個 ViewGroup 內部,而且fragment會定義其本身的視圖佈局。您能夠經過在 Activity 的佈局文件中聲明fragment,將其做爲 <fragment> 元素插入您的 Activity 佈局中,或者經過將其添加到某個現有 ViewGroup,利用應用代碼進行插入。不過,fragment並不是必須成爲 Activity 佈局的一部分;您還能夠將沒有本身 UI 的fragment用做 Activity 的不可見工做線程。bash

本文描述如何在開發您的應用時使用fragment,包括將fragment添加到 Activity 返回棧時如何保持其狀態、如何與 Activity 及 Activity 中的其餘fragment共享事件、如何爲 Activity 的操做欄發揮做用等等。app

設計原理


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

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

您應該將每一個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生命週期方法:動畫

一般,您至少應實現如下生命週期方法:

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 的推進下再次經歷其生命週期。

相關文章
相關標籤/搜索