Android Fragments (Android官方文檔中文版)

概述

        Fragment表現Activity中UI的一個行爲或者一部分。可以將多個fragment組合在一起,放在一個單獨的activity中來創建一個多界面區域的UI,並可以在多個activity裏重用某一個fragment。把fragment想象成一個activity的模塊化區域,有它自己的生命週期,接收屬於它自己的輸入事件,並且可以在activity運行期間添加和刪除.
 
        Fragment必須總是被嵌入到一個activity中。它們的生命週期直接受其宿主activity的生命週期影響。例如:當activity被暫停,那麼在其中的所有fragment也被暫停;當activity被銷燬,所有隸屬於它的fragment也被銷燬。然而,當一個activity正在運行時(處於resumed狀態),我們可以獨立地操作每一個fragment,比如添加或刪除它們。當處理這樣一種fragment事務時,可以將它添加到activity所管理的back stack —— 每一個activity中的back stack實體都是一個發生過的fragment事務的記錄。back stack允許用戶通過按下 BACK 按鍵從一個fragment事務後退(往後導航)。
 
        將一個fragment作爲activity佈局的一部分添加進來時,它處在activity的view hierarchy中的ViewGroup中,並且定義有它自己的view佈局。通過在activity的佈局文件中聲明fragment來插入一個fragment到你的activity佈局中,或者可以寫代碼將它添加到一個已存在的ViewGroup。然而,fragment並不一定必須是activity佈局的一部分;如果願意的話,也可以將一個fragment隱藏在activity的後臺工作。
 
        本文檔描述瞭如何使用fragment創建應用程序,包括:當被添加到activity的back stack後,fragment如何維護它們的狀態。在activity中,與activity和其他fragment共享事件。構建到activity的action bar。以及更多內容。

 設計哲學

        Android在3.0中引入了fragments的概念,主要目的是用在大屏幕設備上——例如平板電腦上,支持更加動態和靈活的UI設計。平板電腦的屏幕要比手機的大得多,有更多的空間來放更多的UI組件,並且這些組件之間會產生更多的交互。Fragment允許這樣的一種設計,而不需要你親自來管理view hierarchy的複雜變化。通過將activity的佈局分散到fragment中,可以在運行時修改activity的外觀,並在由activity管理的back stack中保存那些變化。
 
        例如,一個新聞應用可以在屏幕左側使用一個fragment來展示一個文章的列表,然後在屏幕右側使用另一個fragment來展示一篇文章 —— 2個fragment並排顯示在相同的一個activity中,並且每一個fragment擁有它自己的一套生命週期回調方法,並且處理它們自己的用戶輸入事件。因此,用這種方式來取代使用一個activity來選擇一篇文章,而另一個activity來閱讀文章的模式,用戶可以在相同的activity中選擇一篇文章並且閱讀, 如圖所示:

 
        例如, 一個新聞應用可以在屏幕左側使用一個fragment來展示一個文章的列表, 然後在屏幕右側使用另一個fragment來展示一篇文章 – 2個fragment並排顯示在相同的一個activity中, 並且每一個fragment擁有它自己的一套生命週期回調方法,並且處理它們自己的用戶輸入事件. 因此, 取代 使用一個activity來選擇一篇文章,而另一個activity來閱讀文章 的方式, 用戶可以在相同的activity中選擇一篇文章並且閱讀, 如圖所示:
 
         fragment在你的應用中應當是一個模塊化和可重用的組件. 即,因爲fragment定義了它自己的佈局, 以及通過使用它自己的生命週期回調方法定義了它自己的行爲, 你可以將fragment包含到多個activity中. 這點特別重要, 因爲這允許你將你的用戶體驗適配到不同的屏幕尺寸.舉個例子, 你可能會僅當在屏幕尺寸足夠大時,在一個activity中包含多個fragment, 並且,當不屬於這種情況時,會啓動另一個單獨的,使用不同fragment的activity.
 
         繼續之前那個新聞的例子 -- 當運行在一個特別大的屏幕時(例如平板電腦), app可以在Activity A中嵌入2個fragment. 然而,在一個正常尺寸的屏幕(例如手機)上,沒有足夠的空間同時供2個fragment用, 因此, Activity A 會僅包含文章列表的fragment, 而當用戶選擇一篇文章時, 它會啓動Activity B, 它包含閱讀文章的fragment.因此, 應用可以同時支持圖1中的2種設計模式.
 
創建Fragment

 
    要創建一個fragment, 必須創建一個 Fragment 的子類 (或者繼承自一個已存在的它的子類). Fragment 類的代碼看起來很像 Activity .它包含了和activity類似的回調方法, 例如 onCreate(), onStart(), onPause, 以及 onStop(). 事實上, 如果你準備將一個現成的Android應用轉換到使用fragment, 你可能只需簡單的將代碼從你的activity的回調函數分別移動到你的fragment的回調方法.
 
通常, 應當至少實現如下的生命週期方法:
  • onCreate()
    當創建fragment時, 系統調用此方法.
    在實現代碼中, 應當初始化想要在fragment中保持的必要組件, 當fragment被暫停或者停止後可以恢復.
  • onCreateView()
    fragment第一次繪製它的用戶界面的時候, 系統會調用此方法. 爲了繪製fragment的UI, 此方法必須返回一個View, 這個view是你的fragment佈局的根view. 如果fragment不提供UI, 可以返回null.
  • onPause()
    用戶將要離開fragment時,系統調用這個方法作爲第一個指示(然而它不總是意味着fragment將被銷燬.) 在當前用戶會話結束之前,通常應當在這裏提交任何應該持久化的變化(因爲用戶有可能不會返回).
    大多數應用應當爲每一個fragment實現至少這3個方法, 但是還有一些其他回調方法你也應當用來去處理fragment生命週期的各種階段.全部的生命週期回調方法將會在後面章節 Handling the Fragment Lifecycle 中討論.
 
除了繼承基類 Fragment , 還有一些子類你可能會繼承:
  • DialogFragment
    顯示一個浮動的對話框. 
    用這個類來創建一個對話框,是使用在Activity類的對話框工具方法之外的一個好的選擇,
    因爲你可以將一個fragment對話框合併到activity管理的fragment back stack中, 允許用戶返回到一個之前曾被摒棄的fragment.
  • ListFragment
    顯示一個由一個adapter(例如 SimpleCursorAdapter)管理的項目的列表, 類似於 ListActivity.
    它提供一些方法來管理一個list view, 例如 onListItemClick() 回調來處理點擊事件.
  • PreferenceFragment
    顯示一個 Preference對象的層次結構的列表, 類似於 PreferenceActivity.
    這在爲你的應用創建一個"設置"activity時有用處.
 
添加一個用戶界面

         fragment通常用來作爲一個activity的用戶界面的一部分, 並將它的layout提供給activity.爲了給一個fragment提供一個layout,你必須實現 onCreateView() 回調方法, 當到了fragment繪製它自己的layout的時候, Android系統調用它.你的此方法的實現代碼必須返回一個你的fragment的layout的根view.
 
         注意: 如果你的fragment是ListFragment的子類, 它的默認實現是返回從onCreateView()返回一個ListView, 所以一般情況下不必實現它.
 
        從onCreateView()返回的View, 也可以從一個xml layout資源文件中讀取並生成.爲了幫助你這麼做, onCreateView() 提供了一個 LayoutInflater 對象. 舉個例子, 這裏有一個Fragment的子類, 從文件 example_fragment.xml 加載了一個layout :
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 參數是你的fragment layout將被插入的父ViewGroup(來自activity的layout).savedInstanceState 參數是一個 Bundle, 如果fragment是被恢復的,它提供關於fragment的之前的實例的數據,
 
inflate() 方法有3個參數:
  • 想要加載的layout的resource ID.
  • 加載的layout的父ViewGroup.
    傳入container是很重要的, 目的是爲了讓系統接受所要加載的layout的根view的layout參數,
    由它將掛靠的父view指定.
  • 布爾值指示在加載期間, 展開的layout是否應當附着到ViewGroup (第二個參數).
    (在這個例子中, 指定了false, 因爲系統已經把展開的layout插入到container – 傳入true會在最後的layout中創建一個多餘的view group.)
 
將fragment添加到activity
        通常地, fragment爲宿主activity提供UI的一部分, 被作爲activity的整個view hierarchy的一部分被嵌入.有2種方法你可以添加一個fragment到activity layout:
 
在activity的layout文件中聲明fragment
 
        你可以像爲View一樣, 爲fragment指定layout屬性. 例子是一個有2個fragment的activity:
<span style="font-size:12px;"><?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/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></span>
        <fragment> 中的 android:name 屬性指定了在layout中實例化的Fragment類.
 
        當系統創建這個activity layout時, 它實例化每一個在layout中指定的fragment,並調用每一個上的onCreateView()方法,來獲取每一個fragment的layout. 系統將從fragment返回的 View 直接插入到<fragment>元素所在的地方.
 
        注意: 每一個fragment都需要一個唯一的標識, 如果activity重啓,系統可以用來恢復fragment(並且你也可以用來捕獲fragment來處理事務,例如移除它.)
 
有3種方法來爲一個fragment提供一個標識:
  • 爲 android:id 屬性提供一個唯一ID.
  • 爲 android:tag 屬性提供一個唯一字符串.
  • 如果以上2個你都沒有提供, 系統使用容器view的ID.
 
撰寫代碼將fragment添加到一個已存在的ViewGroup.
        當activity運行的任何時候, 都可以將fragment添加到activity layout.只需簡單的指定一個需要放置fragment的ViewGroup.爲了在你的activity中操作fragment事務(例如添加,移除,或代替一個fragment),必須使用來自 FragmentTransaction 的API.
        可以按如下方法,從你的Activity取得一個 FragmentTransaction 的實例:
 
FragmentManager fragmentManager = getFragmentManager(); 
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        然後你可以使用 add() 方法添加一個fragment, 指定要添加的fragment, 和要插入的view.
 
ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(R.id.fragment_container, fragment); 
fragmentTransaction.commit();
        add()的第一個參數是fragment要放入的ViewGroup, 由resource ID指定, 第二個參數是需要添加的fragment.一旦用FragmentTransaction做了改變,爲了使改變生效,必須調用commit().
添加一個無UI的fragment
 
        之前的例子展示了對UI的支持, 如何將一個fragment添加到activity.然而, 也可以使用fragment來爲activity提供後臺行爲而不用展現額外的UI.
        要添加一個無UI的fragment, 需要從activity使用 add(Fragment, String) 來添加 fragment (爲fragment提供一個唯一的字符串"tag", 而不是一個view ID).這麼做添加了fragment, 但因爲它沒有關聯到一個activity layout中的一個view, 所以不會接收到onCreateView()調用. 因此不必實現此方法.
        爲fragment提供一個字符串tag並不是專門針對無UI的fragment的 – 也可以提供字符串tag給有UI的fragment – 但是如果fragment沒有UI,那麼這個tag是僅有的標識它的途徑. 如果隨後你想從activity獲取這個fragment, 需要使用 findFragmentByTag().
管理Fragment

        要在activity中管理fragment, 需要使用FragmentManager.通過調用activity的getFragmentManager()取得它的實例.
    可以通過FragmentManager做一些事情, 包括:

  • 使用findFragmentById() (用於在activity layout中提供一個UI的fragment)或findFragmentByTag() (適用於有或沒有UI的fragment)獲取activity中存在的fragment
  • 將fragment從後臺堆棧中彈出, 使用 popBackStack() (模擬用戶按下BACK 命令).
  • 使用addOnBackStackChangeListener()註冊一個監聽後臺堆棧變化的listener.

 

處理Fragment事務

        關於在activity中使用fragment的很強的一個特性是: 根據用戶的交互情況,對fragment進行添加,移除,替換,以及執行其他動作.提交給activity的每一套變化被稱爲一個事務, 可以使用在 FragmentTransaction 中的 API 處理.我們也可以保存每一個事務到一個activity管理的back stack,允許用戶經由fragment的變化往回導航(類似於通過activity往後導航).
 
        從 FragmentManager 獲得一個FragmentTransaction的實例 :
FragmentManager fragmentManager = getFragmentManager(); 
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        每一個事務都是同時要執行的一套變化.可以在一個給定的事務中設置你想執行的所有變化,使用諸如 add(), remove(), 和 replace().然後, 要給activity應用事務, 必須調用 commit().
 
        在調用commit()之前, 你可能想調用 addToBackStack(),將事務添加到一個fragment事務的back stack.這個back stack由activity管理, 並允許用戶通過按下 BACK 按鍵返回到前一個fragment狀態.
        舉個例子, 這裏是如何將一個fragment替換爲另一個, 並在後臺堆棧中保留之前的狀態:

// Create new fragment and transaction

Fragment newFragment = new ExampleFragment();

// Replace whatever is in the fragment_container view with this fragment, and add the transaction to the back stack

FragmentTransaction transaction = getFragmentManager().beginTransaction();

transaction.replace(R.id.fragment_container, newFragment);

transaction.addToBackStack(null);

// Commit the transaction

transaction.commit();

 
        在這個例子中, newFragment 替換了當前layout容器中的由R.id.fragment_container標識的fragment.通過調用 addToBackStack(), replace事務被保存到back stack, 因此用戶可以回退事務,並通過按下BACK按鍵帶回前一個fragment.
 
        如果添加多個變化到事務(例如add()或remove())並調用addToBackStack(), 然後在你調用commit()之前的所有應用的變化會被作爲一個單個事務添加到後臺堆棧, BACK按鍵會將它們一起回退.
 
    添加變化到 FragmentTransaction的順序不重要, 除以下例外:
  • 必須最後調用 commit().
  • 如果添加多個fragment到同一個容器, 那麼添加的順序決定了它們在view hierarchy中顯示的順序.

 

        當執行一個移除fragment的事務時, 如果沒有調用 addToBackStack(), 那麼當事務提交後, 那個fragment會被銷燬,並且用戶不能導航回到它. 有鑑於此, 當移除一個fragment時,如果調用了 addToBackStack(), 那麼fragment會被停止, 如果用戶導航回來,它將會被恢復.
 
        提示: 對於每一個fragment事務, 你可以應用一個事務動畫, 通過在提交事務之前調用setTransition()實現.
 
        調用 commit() 並不立即執行事務.恰恰相反, 它將事務安排排期, 一旦準備好, 就在activity的UI線程上運行(主線程).如果有必要, 無論如何, 你可以從你的UI線程調用 executePendingTransactions() 來立即執行由commit()提交的事務.但這麼做通常不必要, 除非事務是其他線程中的job的一個從屬.
        警告: 你只能在activity保存它的狀態(當用戶離開activity)之前使用commit()提交事務.
 
        如果你試圖在那個點之後提交, 會拋出一個異常.這是因爲如果activity需要被恢復, 提交之後的狀態可能會丟失.對於你覺得可以丟失提交的狀況, 使用 commitAllowingStateLoss().
 
與Activity通信

        儘管Fragment被實現爲一個獨立於Activity的對象,並且可以在多個activity中使用, 但一個給定的fragment實例是直接綁定到包含它的activity的.特別的, fragment可以使用 getActivity() 訪問Activity實例, 並且容易地執行比如在activity layout中查找一個view的任務.
 
View listView = getActivity().findViewById(R.id.list);
        同樣地, activity可以通過從FragmentManager獲得一個到Fragment的引用來調用fragment中的方法, 使用 findFragmentById() 或 findFragmentByTag().
 
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

爲Activity創建事件回調方法
        在一些情況下, 你可能需要一個fragment與activity分享事件. 一個好的方法是在fragment中定義一個回調的interface, 並要求宿主activity實現它.當activity通過interface接收到一個回調, 必要時它可以和在layout中的其他fragment分享信息.
        例如, 如果一個新的應用在activity中有2個fragment – 一個用來顯示文章列表(framgent A), 另一個顯示文章內容(fragment B) – 然後 framgent A必須告訴activity何時一個list item被選中,然後它可以告訴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 B,從fragment A到來的事件.爲了確保宿主activity實現這個接口, fragment A的 onAttach() 回調方法(當添加fragment到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沒有實現接口, 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 獲取文章.
 
添加項目到Action Bar
        你的fragment可以通過實現 onCreateOptionMenu() 提供菜單項給activity的選項菜單(以此類推, Action Bar也一樣).爲了使這個方法接收調用,無論如何, 你必須在 onCreate() 期間調用 setHasOptionsMenu() 來指出fragment願意添加item到選項菜單(否則, fragment將接收不到對 onCreateOptionsMenu()的調用)
        隨後從fragment添加到Option菜單的任何項,都會被追加到現有菜單項的後面.當一個菜單項被選擇, fragment也會接收到 對 onOptionsItemSelected() 的回調.也可以在你的fragment layout中通過調用 registerForContextMenu() 註冊一個view來提供一個環境菜單.當用戶打開環境菜單, fragment接收到一個對 onCreateContextMenu() 的調用.當用戶選擇一個項目, fragment接收到一個對onContextItemSelected() 的調用.
        注意: 儘管你的fragment會接收到它所添加的每一個菜單項被選擇後的回調, 但實際上當用戶選擇一個菜單項時, activity會首先接收到對應的回調.如果activity的on-item-selected回調函數實現並沒有處理被選中的項目, 然後事件纔會被傳遞到fragment的回調.

這個規則適用於選項菜單和環境菜單.
 
處理Fragment生命週期

    管理fragment的生命週期, 大多數地方和管理activity生命週期很像.和activity一樣, fragment可以處於3種狀態:
  • Resumed
    在運行中的activity中fragment可見.
  • Paused
    另一個activity處於前臺並擁有焦點, 但是這個fragment所在的activity仍然可見(前臺activity局部透明或者沒有覆蓋整個屏幕).
  • Stopped
    要麼是宿主activity已經被停止, 要麼是fragment從activity被移除但被添加到後臺堆棧中.
    停止狀態的fragment仍然活着(所有狀態和成員信息被系統保持着). 然而, 它對用戶不再可見, 並且如果activity被幹掉,他也會被幹掉.
    仍然和activity一樣, 你可以使用Bundle保持fragment的狀態, 萬一activity的進程被幹掉,並且當activity被重新創建的時候, 你需要恢復fragment的狀態時就可以用到.你可以在fragment的 onSaveInstanceState() 期間保存狀態, 並可以在 onCreate(), onCreateView() 或 onActivityCreated() 期間恢復它.
        生命週期方面activity和fragment之間最重要的區別是各自如何在它的後臺堆棧中儲存.默認地, activity在停止後, 它會被放到一個由系統管理的用於保存activity的後臺堆棧.(因此用戶可以使用BACK按鍵導航回退到它).
        然而, 僅當你在一個事務期間移除fragment時,顯式調用addToBackStack()請求保存實例時,才被放到一個由宿主activity管理的後臺堆棧.
        另外, 管理fragment的生命週期和管理activity生命週期非常類似.因此, "managing the activity lifecycle"中的相同實踐也同樣適用於fragment.你需要理解的是, activity的生命如何影響fragment的生命.
 
與activity生命週期的協調工作
 
        fragment所生存的activity的生命週期,直接影響fragment的生命週期, 每一個activity的生命週期的回調行爲都會引起每一個fragment中類似的回調.
        例如, 當activity接收到onPause()時,activity中的每一個fragment都會接收到onPause().
        Fragment 有一些額外的生命週期回調方法, 那些是處理與activity的唯一的交互, 爲了執行例如創建和銷燬fragment的UI的動作.這些額外的回調方法是:
 
  • onAttach()
    當fragment被綁定到activity時被調用(Activity會被傳入.).
  • onCreateView()
    創建和fragment關聯的view hierarchy時調用.
  • onActivityCreated()
    當activity的onCreate()方法返回時被調用.
  • onDestroyView()
    當和fragment關聯的view hierarchy正在被移除時調用.
  • onDetach()
    當fragment從activity解除關聯時被調用.

 

        fragment生命週期的流程, 以及宿主activity對它的影響, 在圖3中顯示.在這個圖中, 可以看到activity依次的每個狀態是如何決定fragment可能接收到的回調方法.例如, 當activity接收到它的 onCreate(), activity中的fragment接收到最多是onActivityCreated().
        一旦activity到達了resumed狀態, 你可以自由地在activity添加和移除fragment.因此, 僅當activity處於resumed狀態時, fragment的生命週期纔可以獨立變化.
無論如何, 當activity離開resumed狀態, fragment再次被activity的推入它自己的生命週期過程.

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