Fragment必須老是被嵌入到一個activity之中,而且fragment的生命週期直接受其宿主activity的生命週期的影響。例如,一旦activity被暫停,它裏面全部的fragment也被暫停,一旦activity被銷燬,它裏面全部的fragment也被銷燬。然而,當activity正在運行時(處於resumed的生命週期狀態),你能夠單獨的操控每一個fragment,好比添加或者刪除。當你執行這樣一項事務時,能夠將它添加到後臺的一個棧中,這個棧由activity管理着——activity裏面的每一個後臺棧內容實體是fragment發生過的一條事務記錄。這個後臺棧容許用戶經過按BACK鍵回退一項fragment事務(日後導航)。php
當你添加一個fragment做爲某個activity佈局的一部分時,它就存在於這個activity視圖體系內部的ViewGroup之中,而且定義了它本身的視圖佈局。你能夠經過在activity佈局文件中聲明fragment,用<fragment>元素把fragment插入到activity的佈局中,或者是用應用程序源碼將它添加到一個存在的ViewGroup中。然而,fragment並非一個定要做爲activity佈局的一部分;fragment也能夠爲activity隱身工做。html
Android在3.0這個版本中(API Level 「Honeycomb」)引入了fragment的概念,主要是支持在大屏幕上更爲動態和靈活的UI設計。java
例如新聞應用程序將一個fragment放在左邊顯示文章列表,在右邊用另外一個fragment來顯示一篇文章——兩個fragment在同一個activity中並排着,而且每一個fragment都有其本身的生命週期回調方法序列用以處理各自的用戶輸入事件。所以,用戶能夠在同一個activity中選擇和閱讀文章,而不是在一個activity中選擇,在另外一個activity中閱讀。android
應該將每個fragment設計爲模塊化的和可複用化的activity組件。也就是說,你能夠在多個activity中引用同一個fragment,由於fragment定義了它本身的佈局,而且使用它自己生命週期回調的行爲。這點尤其重要,由於它讓你可以改變fragment組合以知足不一樣的屏幕尺寸。在設計同時支持平板電腦和手機的應用時,經過不一樣的佈局配置能夠複用fragments,基於可以使用的屏幕空間優化用戶體驗。例如,在手機上,當同一個activity不能容納更多的fragment時,可能須要經過分離fragments提供一個單區域的界面。api
例如——仍是以那個新聞應用程序爲例——當程序運行在平板尺寸屏幕設備上時,能夠在Activity A中嵌入兩個fragment。可是,當運行在手機尺寸屏幕上,就沒有足夠的空間容納兩個fragment了,所以Activity A只能引用包含文章列表的fragment,在當用戶選擇一篇文章時,能夠啓動Activity B,它包含了用來閱讀文章的第二個fragment。這樣,應用程序經過以不一樣組合的複用fragments支持了平板電腦和手機。app
要建立一個fragment,必須建立一個fragment的子類(或是繼承自它的子類),通常狀況下,你至少須要實現如下幾個生命週期方法:ide
onCreate() 在建立fragment時系統會調用此方法。在實現代碼中,你能夠初始化想要在fragment中保持的那些必要組件,當fragment處於暫停或者中止狀態以後可從新啓用它們。模塊化
onCreateView() 在第一次爲fragment繪製用戶界面時系統會調用此方法。爲fragment繪製用戶界面,這個函數必需要返回所繪出的fragment的根View。若是fragment沒有用戶界面能夠返回空。函數
onPause() 系統回調用該函數做爲用戶離開fragment的第一個預兆(儘管這並不總意味着fragment被銷燬)。在當前用戶會話結束以前,一般要在這裏提交任何應該持久化的變化(由於用戶可能再也不返回)。佈局
除了基類fragment,這裏還有幾個你可能會繼承的子類:
DialogFragment顯示一個浮動的對話框。使用這個類建立對話框是使用Activity類對話框幫助方法以外的另外一個不錯的選擇,由於你能夠把fragment對話框併入到由activity管理的fragments後臺棧中,容許用戶返回到一個已經摒棄的fragment。
ListFragment顯示一個由適配器管理的條目列表(例如SimpleCursorAdapter),相似於ListActivity。而且提供了許多管理列表視圖的函數,例如處理點擊事件的onListItemClick()回調函數。
PreferenceFragment顯示一個Preference對象的體系結構列表,相似於preferenceActivity。這在爲應用程序建立「設置」activity時是很實用的。
添加用戶界面
fragment常被用做activity用戶界面的一部分,而且將自己的佈局構建到activity中去。
爲了給fragment提供一個佈局,你必須實現onCreateView()回調函數,在繪製fragment佈局時Android系統會調用它。實現這個函數時須要返回fragment所屬的根View。
注意:若是你的fragment是ListFragment的子類,默認實現從onCreateView()返回一個ListView,因此你不須要實現它。
爲了從onCreateView()返回一個佈局,你能夠從layout resource定義的XML文件inflate它。爲了便於你這樣作,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); } }
傳入onCreateView()的參數container 是你的frament佈局將要被插入的父ViewGroup(來自activity的佈局)。若是fragment處於resumed狀態(恢復狀態在操縱fragment生命週期一節中將做更多討論),參數savedInstanceState是屬於Bundle類,它提供了fragment以前實例的相關數據。
inflate()函數須要如下三個參數:
要inflate的佈局的資源ID。
被inflate的佈局的父ViewGroup。傳入container很重要,這是爲了讓系統將佈局參數應用到被inflate的佈局的根view中去,由其將要嵌入的父view指定。
一個布爾值,代表在inflate期間被infalte的佈局是否應該附上ViewGroup(第二個參數)。(在這個例子中傳入的是false,由於系統已經將被inflate的佈局插入到容器中(container)——傳入true會在最終的佈局裏建立一個多餘的ViewGroup。)
一般,fragment構建了其宿主activity的部分界面,它被做爲activity所有視圖層次體系的一部分被嵌入進去。在acitivity佈局中添加fragment有兩種方法:
在activity的佈局文件裏聲明fragment
像這樣,你能夠像爲view同樣爲fragment指定佈局屬性。例如,下面含有兩個fragment的佈局文件:
<?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,併爲它們調用onCreateView()函數,以獲取每個fragment的佈局。系統直接在<fragment>元素的位置插入fragment返回的View。
注意:每一個fragment都須要一個惟一的標識,若是重啓activity,系統可用來恢復fragment(而且可用來捕捉fragment的事務處理,例如移除)。爲fragment提供ID有三種方法:
用android:id屬性提供一個惟一的標識。
用android:tag屬性提供一個惟一的字符串。
若是上述兩個屬性都沒有,系統會使用其容器視圖(view)的ID。
或者,經過編碼將fragment添加到已存在的ViewGroup中
在activity運行的任什麼時候候,你均可以將fragment添加到activity佈局中。你僅須要簡單指定用來放置fragment的ViewGroup。
你應當使用FragmentTransaction的API來對activity中的fragment進行操做(例如添加,移除,或者替換fragment)。你能夠像下面這樣從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,它由資源ID(resource ID)指定,第二個參數就是要添加的fragment。
一旦經過FragmentTransaction 作了更改,都應當使用commit()使變化生效。
上面的例子是如何將fragment添加到activity中去,目的是提供一個用戶界面。然而,也可使用fragment爲activity提供後臺動做,卻不呈現多餘的用戶界面。
想要添加沒有界面的fragment ,可使用add(Fragment, String)(爲fragment提供一個惟一的字符串「tag」,而不是視圖(view)ID)。這樣添加了fragment,可是,由於尚未關聯到activity佈局中的視圖(view) ,收不到onCreateView()的調用。因此不須要實現這個方法。
爲無界面fragment提供字符串標籤並非專門針對無界面fragment的——也能夠爲有界面fragment提供字符串標籤——可是對於無界面fragment,字符串標籤是惟一識別它的方法。若是以後想從activity中取到fragment,須要使用findFragmentByTag()。
activity使用fragment在後臺運行可參考例子FragmentRetainInstance.java。
想要管理activity中的fragment,可使用FragmentManager。能夠經過在activity中調用getFragmentManager()得到。
使用FragmentManager 能夠作以下事情,包括:
使用findFragmentById()(用於在activity佈局中提供有界面的fragment)或者findFragmentByTag()獲取activity中存在的fragment(用於有界面或者沒有界面的fragment)。
使用popBackStack()(模仿用戶的BACK命令)從後臺棧彈出fragment。
使用addOnBackStackChangedListener()註冊一個監聽後臺棧變化的監聽器。
關於這些函數和其它的更多信息,可參考FragmentManager類文檔。
在activity中使用fragment的一大特色是具備添加、刪除、替換,和執行其它動做的能力,以響應用戶的互動。提交給activity的每一系列變化被稱爲事務,而且能夠用FragmentTransaction 中的APIs處理。你也能夠將每個事務保存在由activity管理的後臺棧中,而且容許用戶導航回退fragment變動(相似於activity的導航回退)。
你能夠從FragmentManager中獲取FragmentTransaction實例,像這樣:
FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每項事務是在同一時間內要執行的一系列的變動。你能夠爲一個給定的事務用相關方法設置想要執行的全部變化,例如add(),remove(),和replace()。而後,用commit()將事務提交給activity。
然而,在調用commit()以前,爲了將事務添加到fragment事務後臺棧中,你可能會想調用addToBackStatck()。這個後臺棧由activity管理,而且容許用戶經過按BACK鍵回退到前一個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標識的全部的fragment(若是有的話),替代的事務被保存在後臺棧中,所以用戶能夠回退該事務,可經過按BACK鍵還原以前的fragment。
若是添加多個變動事務(例如另外一個add()或者remove())並調用addToBackStack(),那麼在調用commit()以前的全部應用的變動被做爲一個單獨的事務添加到後臺棧中,而且BACK鍵能夠將它們一塊兒回退。
將變動添加到FragmentTransaction中的順序注意如下兩點:
必需要在最後調用commit()
若是你正將多個fragment添加到同一個容器中,那麼添加順序決定了它們在視圖層次(view hierarchy)裏顯示的順序。
在執行刪除fragment事務時,若是沒有調用addToBackStack(),那麼事務一提交fragment就會被銷燬,並且用戶也沒法回退它。然而,當移除一個fragment時,若是調用了addToBackStack(),那麼以後fragment會被中止,若是用戶回退,它將被恢復過來。
提示:對於每個fragment事務,在提交以前經過調用setTransition()來應用一系列事務動做。
調用commit()並不馬上執行事務,相反,而是採起預定方式,一旦activity的界面線程(主線程)準備好即可運行起來。然而,若是有必要的話,你能夠從界面線程調用executePendingTransations()當即執行由commit()提交的事務。但這樣作,一般是沒有必要的,除非其它線程的工做依賴與該項事務。
警告:只能在activity保存狀態(當用戶離開activity時)以前用commit()提交事務。若是你嘗試在那時以後提交,會拋出一個異常。這是由於若是activity須要被恢復,提交後的狀態會被丟失。對於這類丟失提交的狀況,可以使用commitAllowingStateLoss()
儘管Fragment被實現爲一個對象,它獨立於Activity並能夠在多個Activity中使用,一個給定的fragment實例直接被捆綁在包含它的Activity中。
特別是,fragment能夠經過getActivity()函數訪問Activity,而且很容易的執行相似於查找activity佈局中的視圖的任務:
View listView = getActivity().findViewById(R.id.list);
一樣的,activity可以調用fragment的函數findFragmentById()或者findFragmentByTag(),從FragmentManager中獲取Fragment的索引,例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
在一些狀況下,你可能須要fragment與activity共享事件。這樣作的一個好方法是在fragment內部定義一個回調接口,並須要宿主activity實現它。當activity經過接口接收到回調時,能夠在必要時與佈局中的其它fragment共享信息。
舉個例子,若是新聞應用的actvity中有兩個fragment——一個顯示文章列表(fragment A),另外一個顯示一篇文章(fragment B)——而後fragment A 必需要告訴activity列表項什麼時候被選種,這樣,activity能夠通知fragment B顯示這篇文章。這種狀況下,在fragment A內部聲明接口OnArticleSelectedListener:
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會拋出一個ClassCaseException異常。一旦成功,mListener成員會保留一個activity的OnArticleSelectedListener實現的引用,由此fragment A能夠經過調用由OnArticleSelectedListener接口定義的方法與activity共享事件。例如,若是fragment A是ListFragment的子類,每次用戶點擊列表項時,系統都會調用fragment的onListItemClick()事件,而後fragment調用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獲取文章。
關於使用content provider的更多資料能夠在在Content Providers文檔獲取。
你的fragments能夠經過實現onCreateOptionsMenu()來構建菜單項到activity的Options Menu(所以Action Bar也同樣)。爲了使用這個方法接收到調用,無論怎樣,你必須在onCreate()期間調用setHasOptionsMenu(),來指明想要添加項目到Options Menu的那個fragment(不然,fragment將接收不到onCreateOptionsMenu()的調用)。
任何想要在fragment中的Options Menu添加的項目都追加到已有的菜單項後面。當菜單項被選中時,fragment也會接收到對onOptionsItemSelected()的回調。
你也能夠經過調用registerForContextMenu()在fragment佈局中註冊一個view以提供一個context menu。當用戶打開context menu時,fragment接收到對onCreateContextMenu()的回調。當用戶選中一個項目時,fragment接收到對onContextItemSelected()的回調。
注意:儘管你的fragment會接收到爲添加到每一個菜單項被選擇菜單項的回調,但當用戶選擇一個菜單項時,activity會首先接收到對應的回調。若是activity的選擇菜單項回調的實現沒有處理被選中的項目,那麼該事件被傳遞給fragment的回調。這一樣適用於Options Menu和context menu。
關於菜單的更多信息,參考菜單和Action Bar開發指南。
管理fragment生命週期與管理activity生命週期很相像。像activity同樣,fragment也有三種狀態:
Resumed fragment在運行中的activity可見。
Paused 另外一個activity處於前臺且獲得焦點,可是這個fragment所在的activity仍然可見(前臺activity部分透明,或者沒有覆蓋全屏)。
Stopped fragment不可見。要麼宿主activity已經中止,要麼fragment已經從activity上移除,但已被添加到後臺棧中。一箇中止的fragment仍然活着(全部狀態和成員信息仍然由系統保留着)。可是,它對用戶來說已經再也不可見,而且若是activity被殺掉,它也將被殺掉。
同activity相似的還有,你也能夠用Bundle保存fragment狀態,萬一activity的進程被殺掉了,而且在activity被從新建立時,你須要恢復fragment狀態。在回調執行fragment的onSaveInstanceState()期間能夠保存狀態,在onCreate(),onCreateView(),或者onActvityCreate()中能夠恢復狀態。更多關於保存狀態的信息,參考Activities文檔。
在生命週期方面,activity與fragment之間一個很重要的不一樣,就是在各自的後臺棧中是如何存儲的。當activity中止時,默認狀況下,activity被安置在由系統管理的activity後臺棧中(所以用戶能夠按BACK鍵回退導航,就像在Tasks和後臺棧中討論的那樣)。可是,僅當你在一個事務被移除時,經過顯式調用addToBackStack()請求保存的實例,該fragment才被置於由宿主activity管理的後臺棧。
除此以外,管理fragment的生命週期與管理activity的生命週期很是類似。因此,管理activity生命週期的實踐一樣也適用於fragment。你須要瞭解的,僅僅是activity的生命週期如何影響fragment的的。
與activity生命週期的協調合做
fragment所生存的activity生命週期直接影響着fragment的生命週期,由此針對activity的每個生命週期回調都會引起一個fragment相似的回調。例如,當activity接收到onPause()時,這個activity之中的每一個fragment都會接收到onPause()。
Fragment有一些額外的生命週期回調方法,然而,爲了處理像是建立和銷燬fragment界面,它與activity進行獨特的交互。這些額外的回調方法是:
onAttach() 當fragment被綁定到activity時調用(Activity會被傳入)。
onCreateView() 建立與fragment相關的視圖體系時被調用。
onActivityCreated() 當activity的onCreate()函數返回時被調用。
onDestroyView() 當與fragment關聯的視圖體系正被移除時被調用。
onDetach() 當fragment正與activity解除關聯時被調用。
fragment的生命週期流程其實是受其宿主activity影響,如圖3所示。在這張圖中,能夠看到activity的每一個連續狀態是如何決定fragment可能接收到哪一個回調函數的。例如,當activity接收到它的onCreate()回調時,activity之中的fragment接收到的僅僅是onActivityCreated()回調。
一旦activity處於resumed狀態,則能夠在activity中自由的添加或者移除fragment。所以,只有當activity處於resumed狀態時,fragment的生命週期才能夠獨立變化。
然而,當activity離開恢復狀態時,fragment再一次被activity推入它的生命週期中