Fragment 的基礎知識介紹 1.1 概述java
1.1.1 特性數據庫
ragment 是 activity 的界面中的一部分或一種行爲。能夠把多個 Fragment 組合到一個 activity 中來建立一 個多面界面而且能夠在多個 activity 中重用一個 Fragment。能夠把 Fragment 認爲模塊化的一段 activity,它具 有本身的生命週期,接收它本身的事件,並能夠在 activity 運行時被添加或刪除。ide
Fragment 不能獨立存在,它必須嵌入到 activity 中,並且 Fragment 的生命週期直接受所在的 activity 的影 響。例如:當 activity 暫停時,它擁有的全部的 Fragment 都暫停了,當 activity 銷燬時,它擁有的全部 Fragment 都被銷燬。然而,當 activity 運行時(在 onResume()以後,onPause()以前),能夠單獨地操做每一個 Fragment, 好比添加或刪除它們。當在執行上述針對 Fragment 的事務時,能夠將事務添加到一個棧中,這個棧被 activity 管 理,棧中的每一條都是一個 Fragment 的一次事務。有了這個棧,就能夠反向執行 Fragment 的事務,這樣就能夠在 Fragment 級支持「返回」鍵(向後導航)。模塊化
當向 activity 中添加一個 Fragment 時,它須置於 ViewGroup 控件中,而且需定義 Fragment 本身的界面。可 以在 layoutxml 文件中聲明 Fragment,元素爲:<fragment>;也能夠在代碼中建立 Fragment,而後把它加入到 ViewGroup 控件中。然而,Fragment 不必定非要放在 activity 的界面中,它能夠隱藏在後臺爲 actvitiy 工做。函數
1.1.2 生命週期佈局
onCreate():
當建立 fragment 時系統調用此方法。在其中必須初始化 fragment 的基礎組件們。可參考 activity 的說明。 onCreateView():
系統在 fragment 要畫本身的界面時調用(在真正顯示以前)此方法。這個方法必須返回 frament 的 layout 的根控 件。若是這個 fragment 不提供界面,那它應返回 null。
onPause():
大多數程序應最少對 fragment 實現這三個方法。固然還有其它幾個回調方法可應該按狀況實現之。全部的生命周 期回調函數在「操控 fragment 的生命週期」一節中有詳細討論。this
下圖爲 fragment 的生命週期(它所在的 activity 處於運行狀態)。spa
添加Fragments線程
onAttach()code
onCreate()
onCreateView()
onActivityCreated()
onStart()
onResume()
Fragments是活動的(正在使 用)
Fragment從
返回堆棧中 返回到佈局文件
用戶使用返 回功能或 Fragments 被移除 (替換)
Fragments被
添加到返回 堆棧中,接着 被移除(替換)
onPause()
onStop
onDestroyView()
onDestroy()
onDetach()
Fragments被銷燬
1.1.3 派生類
DialogFragment
顯示一個浮動的對話框。使用這個類建立對話框是替代 activity 建立對話框的最佳選擇.由於能夠把 fragmentdialog 放入到 activity 的返回棧中,使用戶能再返回到這個對話框。
ListFragment
顯示一個列表控件,就像 ListActivity 類,它提供了不少管理列表的方法,好比 onListItemClick()方法響應 click 事件。 PreferenceFragment
顯示一個由 Preference 對象組成的列表,與 PreferenceActivity 相同。它用於爲程序建立「設置」activity。
1.2 範例
寫一個讀新聞的程序,能夠用一個 fragment 顯示標題列表,另外一個 fragment 顯示選中標題的內容,這兩個 fragment 都在一個 activity 上,並排顯示。那麼這兩個 fragment 都有本身的生命週期並響應本身感興趣的事件。於 是,不需再像手機上那樣用一個 activity 顯示標題列表,用另外一個 activity 顯示新聞內容;如今能夠把二者放在一個 activity 上同時顯示出來。以下圖:
圖 2 Fragment 說明性示例
Fragment 必須被寫成可重用的模塊。由於 fragment 有本身的 layout,本身進行事件響應,擁有本身的生命週期
和行爲,因此能夠在多個 activity 中包含同一個 Fragment 的不一樣實例。這對於讓界面在不一樣的屏幕尺寸下都能給用 戶完美的體驗尤爲重要。好比能夠在程序運行於大屏幕中時啓動包含不少 fragment 的 activity,而在運行於小屏幕 時啓動一個包含少許 fragment 的 activity。
剛纔讀新聞的程序,當檢測到程序運行於大屏幕時,啓動 activityA,將標題列表和新聞內容這兩個 fragment 都 放在 activityA 中;當檢測到程序運行於小屏幕時,仍是啓動 activityA,但此時 A 中只有標題列表 fragment,當選中 一個標題時,activityA 啓動 activityB,B 中含有新聞內容 fragment。
1.3 建立 Fragmet
要建立 fragment,必須從 Fragment 或 Fragment 的派生類派生出一個類。Fragment 的代碼寫起來有些像 activity。它 具備跟 activity 同樣的回調方法,好比 onCreate(),onStart(),onPause()和 onStop()。實際上,若是想把老的程序改成使 用 fragment,基本上只須要把 activity 的回調方法的代碼移到 fragment 中對應的方法便可。
1.3.1 添加有界面的 Fragment
Fragment 通常做爲 activity 的用戶界面的一部分,把它本身的 layout 嵌入到 activity 的 layout 中。一個要爲 fragment 提供 layout,必須實現 onCreateView()回調方法,而後在這個方法中返回一個 View 對象,這個對象是 fragment 的 layout 的根。
注意:若是的 fragment 是從 ListFragment 中派生的,就不須要實現 onCreateView()方法了,由於默認的實現已 經爲返回了 ListView 控件對象。
要從 onCreateView()方法中返回 layout 對象,能夠從 layoutxml 中生成 layout 對象。爲了幫助這樣作, onCreateView()提供了一個 LayoutInflater 對象。舉例:如下代碼展現了一個 Fragment 的子類如何從 layoutxml 文件 example_fragment.xml 中生成對象。
PublicstaticclassExamp leFragmentextendsFragment{ @Override Public View onCreate View (LayoutInflat erinflater,View Groupcontainer, BundlesavedInstanceState){ //Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment ,container,false); } }
onCreateView()參數中的 container 是存放 fragment 的 layout 的 ViewGroup 對象。savedInstanceState
參數是一個Bundle,跟 activity的onCreate()中 Bundle差很少,用於狀態恢復。可是 fragment的onCreate() 中也有 Bundle 參數,因此此處的 Bundle 中存放的數據與 onCreate()中存放的數據仍是不一樣的。
Inflate()方法有三個參數:
layout 的資源 ID。存放 fragment 的 layout 的 ViewGroup。
布爾型數據表示是否在建立 fragment 的 layout 期間,把 layout 附加到 container 上(在這個例子
中,由於系統已經把 layout 插入到 container 中了,因此值爲 false,若是爲 true 會導至在最終的 layout 中建立多餘的 ViewGroup)。
下面講述如何把它添加到 activity 中。把 fragment 添加到 activity 通常狀況下,fragment 把它的 layout 做爲 activitiy 的 loyout 的一部分合併到 activity 中,有兩種方法將一個 fragment 添加到 activity 中:
方法一:在 activity 的 layoutxml 文件中聲明 fragment 以下代碼,一個 activity 中包含兩個 fragment:
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayout xmlns:Android="http ://schemas.Android.co m/ap k/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>中聲明一個 fragment。當系統建立上例中的 layout 時,它實例化每個 fragment,然 後調用它們的 onCreateView()方法,以獲取每一個 fragment 的 layout。系統把 fragment 返回的 view 對象插入到<fragment> 元素的位置,直接代替<fragment>元素。
注:每一個 fragment 都須要提供一個 ID,系統在 activity 從新建立時用它來恢復 fragment,也能夠用它來操做 fragment 進行其它的事物,好比刪除它。有三種方法給 fragment 提供 ID:
爲 Android:id 屬性賦一個數字。
爲 Android:tag 屬性賦一個字符串。
若是沒有使用上述任何一種方法,系統將使用 fragment 的容器的 ID。
方法二:在代碼中添加 fragment 到一個 ViewGroup
這種方法能夠在運行時,把 fragment 添加到 activity 的 layout 中。只需指定一個要包含 fragment 的 ViewGroup。
爲了完成 fragment 的事務(好比添加,刪除,替換等),必須使用 FragmentTransaction 的方法。
取到 FragmentTransaction,以下:
FragmentManagerfragmentManager =getFragmentManager() FragmentTransactionfragmentTransaction=fragmentManager.beginTransaction();
而後能夠用 add()方法添加一個 fragment,它有參數用於指定容納 fragment 的 ViewGroup。如,Add()的第一個 參數是容器 ViewGroup,第二個是要添加的 fragment。一旦經過 FragmentTransaction 對 fragment 作出了改變,必須 調用方法 commit()提交這些改變。不只在無界面的 fragment 中,在有界面的 fragment 中也可使用 tag 來做爲爲一 標誌,這樣在須要獲取 fragment 對象時,要調用 findFragmentTag()。
1.3.2 添加沒有界面的 Fragment
上面演示瞭如何添加 fragment 來提供界面,然而,也可使用 fragment 爲 activity 提供後臺的行爲而不用顯示 fragment 的界面。要添加一個沒有界面的 fragment,需在 activity 中調用方法 add(Fragment,String)(它支持用一個惟 一的字符串作爲 fragment 的「tag」,而不是 viewID)。這樣添加的 fragment 因爲沒有界面,因此在實現它時不需 調用實現 onCreateView()方法。
使用 tag 字符串來標識一個 fragment 並非只能用於沒有界面的 fragment 上,也能夠把它用於有界面的 fragment 上,可是,若是一個 fragment 沒有界面,tag 字符串將成爲它惟一的選擇。獲取以 tag 標識的 fragment,需使用方法 findFragmentByTab()。
1.4 Frament 管理
要管理 fragment,需使用 FragmentManager,要獲取它,需在 activity 中調用方法 getFragmentManager()。 能夠用 FragmentManager 來作以上事情:
使用方法 findFragmentById()或 findFragmentByTag(),獲取 activity 中已存在的 fragment
使用方法 popBackStack()從 activity 的後退棧中彈出 fragment(這能夠模擬後退鍵引起的動做)
用方法 addOnBackStackChangedListerner()註冊一個偵聽器以監視後退棧的變化
還可使用 FragmentManager 打開一個 FragmentTransaction 來執行 fragment 的事務,好比添加或刪除 fragment。
在 activity 中使用 fragment 的一個偉大的好處是能跟據用戶的輸入對 fragment 進行添加、刪除、替換以及執行 其它動做的能力。提交的一組 fragment 的變化叫作一個事務。事務經過 FragmentTransaction 來執行。還能夠把每一個 事務保存在 activity 的後退棧中,這樣就可讓用戶在 fragment 變化之間導航(跟在 activity 之間導航同樣)。
能夠經過 FragmentManager 來取得 FragmentTransaction 的實例,以下:
FragmentManagerfragmentManager = getFragmentManager();
FragmentTransactionfragmentTransaction =fragmentManager.beginTransaction(); 一個事務是在同一時刻執行的一組動做(很像數據庫中的事務)。能夠用 add(),remove(),replace()等方法構成事
務,最後使用 commit()方法提交事務。在調用 commint()以前,能夠用 addToBackStack()把事務添加到一個後退棧中, 這個後退棧屬於所在的 activity。有了它,就能夠在用戶按下返回鍵時,返回到 fragment 執行事務以前的狀態。如 下例:演示瞭如何用一個 fragment 代替另外一個 fragment,同時在後退棧中保存被代替的 fragment 的狀態。
//Create new fragment and transaction Fragment newFragment = newExampleFragment(); FragmentTransaction transaction=getFragmentManager().beginTransaction(); //Replace whatever is in the fragment_container view with thisfragment, //and add the transaction to the backstack t ransact ion.rep lace(R.id.fra gm ent _cont ainer,new Fra gment ); transaction.addToBackStack(null) ; //Commit the transaction transaction.commit();
解釋:newFragment 代替了控件 IDR.id.fragment_container 所指向的 ViewGroup 中所含的任何 fragment。而後調 用 addToBackStack(),此時被代替的 fragment 就被放入後退棧中,因而當用戶按下返回鍵時,事務發生回溯,原先 的 fragment 又回來了。
若是向事務添加了多個動做,好比屢次調用了 add(),remove()等以後又調用了 addToBackStack()方法,那麼全部 的在 commit()以前調用的方法都被做爲一個事務。當用戶按返回鍵時,全部的動做都被反向執行(事務回溯)。
事務中動做的執行順序可隨意,但要注意如下兩點:
必須最後調用 commit()
若是添加了多個 fragment,那麼它們的顯示順序跟添加順序一至(後顯示的覆蓋前面的) 若是在執行的事務中有刪除 fragment 的動做,並且沒有調用 addToBackStack(),那麼當事務提交時,那些被刪
除的 fragment 就被銷燬了。反之,那些 fragment 就不會被銷燬,而是處於中止狀態。當用戶返回時,它們會被恢復。 可是,調用 commit()後,事務並不會立刻執行。它會在 activity 的 UI 線程(其實就是主線程)中等待直到線程 能執行的時候才執行(廢話)。若是必要,能夠在 UI 線程中調用 executePendingTransactions()方法來當即執行事務。
但通常不需這樣作,除非有其它線程在等待事務的執行。
注意:只能在 activity 處於可保存狀態的狀態時,好比 running 中,onPause()方法和 onStop()方法中提交事務, 不然會引起異常。這是由於 fragment 的狀態會丟失。若是要在可能丟失狀態的狀況下提交事務,請使用 commitAllowingStateLoss()。
1.5 Fragment 與 Activity 通信
儘管 fragment 的實現是獨立於 activity 的,能夠被用於多個 activity,可是每一個 activity 所包含的是同一個 fragment 的不一樣的實例。Fragment 能夠調用 getActivity()方法很容易的獲得它所在的 activity 的對象,而後就能夠查找 activity 中的控件們(findViewById())。例如:
ViewlistView =getActivity().findViewById(R.id.list);一樣的,activity 也能夠經過 FragmentManager 的方 法查找它所包含的 frament 們。
例如:
ExampleFragment fragment =(ExampleFragment)getFragmentManager().findFragmentById(R.id.example_fragment )
有時,可能須要 fragment 與 activity 共享事件。一個好辦法是在 fragment 中定義一個回調接口,而後在 activity 中實現之。例如,仍是那個新聞程序的例子,它有一個 activity,activity 中含有兩個 fragment。fragmentA 顯示新聞標題,fragmentB 顯示標題對應的內容。fragmentA 必須在用戶選擇了某個標題時告訴 activity,而後 activity 再告訴 fragmentB,fragmentB 就顯示出對應的內容。
以下例,OnArticleSelectedListener 接口在 fragmentA 中定義:
public static class FragmentA extends ListFragment{ //Container Activity must implement this interface public interface OnArticleSelectedListener{ public void onArticleSelected(Uri articleUri); }
而後 activity 實現接口 OnArticleSelectedListener,在方法 onArticleSelected()中通知 fragmentB。當 fragment 添加到 activity 中時,會調用 fragment 的方法 onAttach(),這個方法中適合檢查 activity 是否實現了
OnArticleSelectedListener 接口,檢查方法就是對傳入的 activity 的實例進行類型轉換,以下所示:
public static class FragmentA extends ListFragment{ OnArticleSelectedListenermListener; ... @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 成員變 量保存 OnArticleSelectedListener 的實例。因而 fragmentA 就能夠調用 mListener 的方法來與 activity 共享事 件。例如,若是 fragmentA 是一個 ListFragment,每次選中列表的一項時,就會調用 fragmentA 的 onListItemClick() 方法,在這個方法中調用 onArticleSelected()來與 activity 共享事件,以下:
public static class FragmentA extends ListFragment{ OnArticleSelectedListenermListener; ... @Override public void onListItemClick(ListViewl,Viewv,intposition,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,另外一個 fragment 用這個 ID 來從程序的
ContentProvider 中取得標題的內容。