概述
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定義了它本身的佈局,以及經過使用它本身的生命週期回調方法定義了它本身的行爲,所以能夠將fragment包含到多個activity中。這點特別重要,由於這容許你將你的用戶體驗適配到不一樣的屏幕尺寸。舉個例子,你可能會僅當在屏幕尺寸足夠大時,在一個activity中包含多個fragment,當不屬於這種狀況時,會啓動另外一個單獨的、使用不一樣fragment的activity。
繼續以前那個新聞的例子 —— 當運行在一個特別大的屏幕時(例如平板電腦),app能夠在Activity A中嵌入2個fragment。然而,在一個正常尺寸的屏幕(例如手機)上,沒有足夠的空間同時供2個fragment用,所以,Activity A 會僅包含文章列表的fragment,而當用戶選擇一篇文章時,它會啓動Activity B,它包含閱讀文章的fragment。所以,應用能夠同時支持圖1中的2種設計模式。
要建立一個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生命週期的各類階段。所有的生命週期回調方法將會在後面章節 《處理Fragment生命週期》 中討論。
除了繼承基類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:android
1 |
public static class ExampleFragment extends Fragment { |
3 |
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
5 |
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:設計模式
01 |
<? xml version = "1.0" encoding = "utf-8" ?> |
02 |
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" |
03 |
android:orientation = "horizontal" |
04 |
android:layout_width = "match_parent" |
05 |
android:layout_height = "match_parent" > |
06 |
< fragment android:name = "com.example.news.ArticleListFragment" |
07 |
android:id = "@+id/list" |
08 |
android:layout_weight = "1" |
09 |
android:layout_width = "0dp" |
10 |
android:layout_height = "match_parent" /> |
11 |
< fragment android:name = "com.example.news.ArticleReaderFragment" |
12 |
android:id = "@+id/viewer" |
13 |
android:layout_weight = "2" |
14 |
android:layout_width = "0dp" |
15 |
android:layout_height = "match_parent" /> |
<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。app
1 |
ExampleFragment fragment = new ExampleFragment(); |
2 |
fragmentTransaction.add(R.id.fragment_container, fragment); |
3 |
fragmentTransaction.commit(); |
add()的第一個參數是fragment要放入的ViewGroup,由resource ID指定,第二個參數是須要添加的fragment。一旦用FragmentTransaction做了改變,爲了使改變生效,必須調用commit()。ide
添加一個無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。
- 使用 popBackStack() (模擬用戶按下BACK 命令),將fragment從後臺堆棧中彈出。
- 使用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替換爲另外一個,並在後臺堆棧中保留以前的狀態:模塊化
2 |
Fragment newFragment = new ExampleFragment(); |
3 |
FragmentTransaction transaction = getFragmentManager().beginTransaction(); |
6 |
transaction.replace(R.id.fragment_container, newFragment); |
7 |
transaction.addToBackStack( null ); |
在這個例子中,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中聲明:函數
1 |
public static class FragmentA extends ListFragment { |
4 |
public interface OnArticleSelectedListener { |
5 |
public void onArticleSelected(Uri articleUri); |
而後fragment的宿主activity實現 OnArticleSelectedListener 接口,並覆寫 onArticleSelected() 來通知fragment B,從fragment A傳來了事件。爲了確保宿主activity實現這個接口,fragment A的 onAttach() 回調方法(當添加fragment到activity時由系統調用)經過將做爲參數傳入onAttach()的Activity作類型轉換來實例化一個OnArticleSelectedListener實例。工具
01 |
public static class FragmentA extends ListFragment { |
02 |
OnArticleSelectedListener mListener; |
05 |
public void onAttach(Activity activity) { |
06 |
super .onAttach(activity); |
08 |
mListener = (OnArticleSelectedListener) activity; |
09 |
} catch (ClassCastException e) { |
10 |
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。佈局
01 |
public static class FragmentA extends ListFragment { |
02 |
OnArticleSelectedListener mListener; |
05 |
public void onListItemClick(ListView l, View v, int position, long id) { |
07 |
Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id); |
09 |
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生命週期很是相似。所以,《管理activity生命週期》一章中的相同實踐也一樣適用於fragment。你須要理解的是,activity的生命期如何影響fragment的生命期.
與activity生命週期的協調工做
fragment所生存的activity的生命週期,直接影響fragment的生命週期,每個activity的生命週期的回調行爲都會引發每個fragment中相似的回調。
例如,當activity接收到onPause()時,activity中的每個fragment都會接收到onPause()。
爲了執行例如建立和銷燬fragment的UI的動做,Fragment有一些額外的生命週期回調方法,它們是處理與activity交互的惟一渠道。這些額外的回調方法是:
- 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帶入其生命週期過程。