今天周三,來自老熟人 劉明淵 的文章,想必大家都懂了。來自官方的也是最全面的譯文資料,這裡有你想要了解的關於Fragment的一切。
劉明淵 的博客地址:http://blog.csdn.net/vanpersie_9987
Fragment 是 Android API 中的一個類,它代表Activity中的一部分界面;您可以在一個 Activity 界面中使用多個 Fragment,或者在多個 Activity 中重用某一個 Fragment。
本文將介紹 Fragment 的 定義、創建、添加、移除、生命周期 等,如需訪問官方原文,您可以點擊這個連結(大多數情況下你得藉助某些工具才可以訪問):
https://developer.android.com/guide/components/fragments.html
可以把 Fragment 想像成 Activity 中的一個模塊,它有自己的生命周期、可以接收輸入事件、可以在 Activity 運行時將 Fragment 動態添加和移除等。
Fragment 必須嵌入在 Activity 中才能生存,其生命周期也直接受宿主 Activity 的生命周期的影響。比如,若宿主 Activity 處於 pause 狀態,它所管轄的 Fragment 也將進入 pause 狀態。而當 Activity 處於 resume 狀態的時候,您可以獨立地控制每一個 Fragment,如添加或刪除等。
當執行一個 Fragment 事務時,也可以將該 Fragment 加入到一個由宿主 Activity 管轄的後退棧中,並由 Activity 記錄加入到後退棧的 Fragment 信息,按下後退鍵可以將 Fragment 從後退棧中一次彈出。
將 Fragment 添加至 Activity 的視圖布局中有兩種方式:一種是使用fragment標籤加入,Fragment的父視圖應是一個ViewGroup;另一種使用代碼動態加入,並將一個ViewGroup作為Fragment的容器。
在某些情況下,fragment並不作為Activity視圖展示的一部分,它可能只是用來作為非顯示性的功能。
Fragment 是 Android 3.0 (API level 11) 新加入的API,主要的設置目的是為了使UI在不同的屏幕上表現得更加靈活。由於平板比手機屏幕大的多,因此平板上可以呈現更多的內容,而 Fragment 可以實現同一視圖布局在不同大小的屏幕上顯示不同的效果,將 Fragment 加入到 Activity 的 Layout 中,可以在運行時動態替換 Fragment 並將 Fragment 保存至 Activity 管轄的後退棧中。
比如說,一個新聞應用程式運行在平板上時,一個 Activity 視圖界面可以裝載兩個 Fragment,其中左邊的 Fragment 用於顯示新聞的標題,而右邊的 Fragment 用於顯示相應的新聞內容;若將該應用運行在手機上,一個 Activity 視圖界面無法裝載兩個 Fragment,故將兩個 Fragment 分別裝載到兩個 Activity中,如下所示:
為了創建Fragment,需要繼承一個 Fragment 類,並實現 Fragment 的生命周期回調方法,如 onCreate(), onStart(), onPause(), onStop() 等。事實上,若需要在一個應用中加入 Fragment,只需要將原來的 Activity 替換為 Fragment,並將 Activity 的生命周期回調方法簡單地改為 Fragment 的生命周期回調方法即可。
一般來說,在 Fragment 中應至少重寫這些生命周期方法:
onCreate():當創建 Fragment 實例時,系統回調的方法。在該方法中,需要對一些必要的組件進行初始化,以保證這個組件的實例在 Fragment 處於 pause或stop 狀態時仍然存在。
onCreateView():當第一次在 Fragment 上繪製UI時,系統回調的方法。該方法返回一個 View 對象,該對象表示 Fragment 的根視圖;若 Fragment 不需要展示視圖,則該方法可以返回 null。
onPause():當用戶離開 Fragment 時回調的方法(並不意味著該 Fragment 被銷毀)。在該方法中,可以對 Fragment 的數據信息做一些持久化的保存工作,因為用戶可能不再返回這個 Fragment。
大多數情況下,需要重寫上述三個方法,有時還需要重寫其他生命周期方法,Fragment 的生命周期如下所示:
為了方便起見,繼承下面這些特殊的Fragment可以簡化其初始化過程:
DialogFragment:可展示一個懸浮的對話框。使用該類創建的對話框可以很好地替換由 Activity 類中的方法創建的對話框,因為您可以像管理其他 Fragment 一樣管理 DialogFragment——它們都被壓入由宿主 Activity 管理的 Fragment 棧中,這可以很方便的找回已被壓入棧中的 Fragment。
ListFragment:可以展示一個內置的 AdapterView,該 AdapterView 由一個 Adapter 管理著,如 SimpleCursorAdapter。ListFragment 類似於 ListActivity,它提供了大量的用於管理 ListView 的方法,比如回調方法 onListItemClick(),它用於處理點擊項事件。
PreferenceFragment:可以展示層級嵌套的 Preference 對象列表。PreferenceFragment 類似於 PreferenceActivity,該類一般用於為應用程式編寫設置頁面。
必須重寫 onCreateView() 方法,為 Fragment 綁定布局,該方法返回的 View 就是 Fragment 的根視圖。
!請注意:若繼承的 Fragment 是 ListFragment,onCreateView() 方法已默認返回了 ListView 對象,故無需再重寫該方法。
下面是一個將 example_fragment.xml 布局文件綁定至 ExampleFragment 的示例:
方法回傳的第二個參數 ViewGroup 來自宿主 Activity 容器布局,Fragment 的布局將其作為根視圖插入至該視圖中(is the parent ViewGroup (from the activity’s layout) in which your fragment layout will be inserted)。第三個參數 Bundle 用於回傳之前佔據該位置的 Fragment 實例所保存的 Bundle 信息,當該 Fragment 的新實例處於 resume 狀態時,該參數被回傳(provides data about the previous instance of the fragment, if the fragment is being resumed )。
inflate() 方法需要三個參數:
參數1(int):需要綁定的Layout的資源ID;
參數2(ViewGroup):綁定的Layout布局的父視圖;
參數3(boolean):是否需要將參數1的Layout資源依附於(should be attached to)參數2的ViewGroup上,上例中為false,表示不依附。(系統已經默認將Layout插入至ViewGroup中,若為true,將添加一層冗餘的視圖(redundant view group in the final layout))。
一般地,Fragment 綁定的UI作為宿主 Activity 的一部分,嵌套在整個 Activity 層級視圖中。將 Fragment 加入 Activity ,有如下兩種方式:
方式一:使用fragment標籤加入
其中標籤 <fragment> 中的屬性 android:name 指定 Fragment 的全限定類名(specifies the Fragment class to instantiate in the layout)。
當系統加載 Activity 的 layout 視圖時,同時加載 Fragment 綁定的視圖,並回調相應 Fragment 的 onCreateView() 方法,系統將 <fragment> 標籤替換為 onCreateView() 方法返回的 View。
!請注意:必須為fragment設定唯一的身份標識,以便當宿主Activity為restart狀態時可以恢復(restore)fragment。
有三種為fragment設置唯一標識的方式:
通過 android:id 屬性為 fragment 指定唯一ID;
通過 android:tag 屬性為 fragment 指定唯一字符串標識;
若上述兩種凡是均未指定,則該 fragment 的標識為其父容器控制項的ID(the system uses the ID of the Container view)。
方式二:編寫代碼將 fragment 動態添加至現存的 ViewGroup(Or, programmatically add the fragment to an existing ViewGroup)
當 Activity 處於 running 狀態時,可以將 fragment 添加至 Activity 布局 layout 中,您只需要指定 fragment 的父容器就行了。
為了在 Activity 中對 fragment 做添加、刪除、替換等操作(add, remove, or replace a fragment),需調用 FragmentTransaction:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
使用 add() 方法添加 fragment,並指定其添加位置,最後調用 commit() 方法提交事務:
ExampleFragment fragment = new ExampleFragment();fragmentTransaction.add(R.id.fragment_container, fragment);fragmentTransaction.commit();
有時,為了讓 fragment 執行一些後臺行為(background behavior),可以不為 fragment 綁定UI。
為了給 Activity 添加這種不帶UI的 fragment,需調用 add(Fragment, String) 方法,其中第二個參數 String 是為 fragment 指定一個唯一的 tag 標籤,而非指定 View的ID(supplying a unique string 「tag」 for the fragment, rather than a view ID)。由於未綁定UI,故無需重寫 onCreateView() 方法。
用 String 標籤為未綁定UI的 fragment 指定唯一標識並不嚴謹(Supplying a string tag for the fragment isn’t strictly for non-UI fragments),您也可以給綁定了UI的 fragment 指定 String 標籤;但是若某個 fragment 未綁定UI,那麼只能用 String 標籤來標識該 fragment,若需要在 Activity 中獲取該 fragment,需調用 findFragmentByTag() 方法。
在開發者下載的SDK中,有一個不帶UI的fragment的示例程序 FragmentRetainInstance.Java ,它的路徑為:
<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java
為了在 Activity 中管理 Fragment,需調用 getFragmentManager() 方法獲取 FragmentManager 實例。您可以使用 FragmentManager 完成如下操作:
調用 findFragmentById() 方法獲取由 Activity 管轄的綁定了UI的 Fragment 實例(for fragments that provide a UI in the activity layout);調用 findFragmentByTag() 方法獲取由 Activity 管轄的未綁定UI的 Fragment 實例(for fragments that do or don’t provide a UI);
調用 popBackStack() 方法將 Fragment 從後退棧中彈出;
調用 addOnBackStackChangedListener() 方法註冊監聽器,用於監聽後退棧的變化。
使用 Fragment 的最大好處就是可實現動態添加、刪除、替換 等 操作,實現與用戶的交互。每一組向 Activity 提交的變化稱為事務(Each set of changes that you commit to the activity is called a transaction),您可以使用 FragmentTransaction 這個API操作事務。您也可以將事務保存在由 Activity 管轄的後退棧中,以方便用戶點擊後退鍵來查看 Fragment 的變化。
使用示例如下:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每個事務都是您希望同時執行的一組變化(Each transaction is a set of changes that you want to perform at the same time.),這些變化包括 add(), remove(), 和 replace() 等操作,最後,為了使事務在Activity中生效,需調用 commit() 方法。
在調用 commit() 方法之前,可以調用 addToBackStack() 方法,將該事物添加到由宿主 Activity 管轄的 Fragment 後退棧中。通過點擊後退鍵,用戶可以查看該後退棧中的內容。
舉例如下:
在上述示例中,將 newFragment 對象所綁定的UI視圖替換至以 R.id.fragment_container 為ID的 layout 容器中,並調用 addToBackStack() 方法將該事物添加至後退棧中。這樣,這一事務就被保存到了後退棧中,用戶可以找回該事物;另外,點擊 back按鈕 也可以顯示替換之前的 Fragment。
若您在一組事務中進行了不止一項操作(如同時調用了 add()、remove() 等方法),並調用 addToBackStack() 方法將該事務加入後退棧中,那麼在調用 commit() 之前,這些操作將作為一個事務整體存在於後退棧中,用戶點擊 back鍵 將會整體回退。
使用 FragmentTransaction 操作事務時,操作順序是沒有規定的,但以下兩點必須注意:
若在事務中進行了 remove 操作,而且在提交事務之前未調用 addToBackStack() 方法,那麼該 Fragment 會被 destroy,用戶點擊back鍵將無法找回;相反,若調用了 addToBackStack(),那麼 Fragment 將處於 stopped 狀態,用戶點擊back鍵將可以找回。
對於每個 Fragment 事務,您可以在調用 commit() 方法之前調用 setTransition() 方法,為事務的變化添加動畫。
調用 commit() 方法並不會立即執行事務,因為執行事務是在UI線程(主線程)中進行的,只有當主線程空閒時,才會執行事務操作(it schedules it to run on the activity’s UI thread (the 「main」 thread) as soon as the thread is able to do so)。如有必要,可以在UI線程中調用 executePendingTransactions() 方法,以便在 commit() 方法調用後立即執行提交的事務。但一般沒必要這麼做,除非事務的操作依賴於其他線程(Doing so is usually not necessary unless the transaction is a dependency for jobs in other threads)。
調用 commit() 提交事務的時機應是「Activity保存狀態之前」( prior to the activity saving its state),即用戶離開 Activity 之前。若試圖在這個時機之後調用 commit(),系統將拋出異常。
儘管一個 Fragment 實例獨立於一個 Activity,並且一個 Fragment 可以嵌入到多個 Activity 中(可以重用),但 Activity 包含的 Fragment 將直接有這個 Activity 管轄。
具體來說,Fragment 可以通過 getActivity() 方法獲取其宿主 Activity 的對象引用,通過該引用,可以調用 Activity 中的 findViewById() 方法獲得布局中的視圖控制項:
View listView = getActivity().findViewById(R.id.list);
類似地,也可以在 Activity 中獲取 Fragment 的實例:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
有些情況下,您需要 Fragment 響應 Activity 的事件,好的做法是在 Fragment中添加回調接口,並在其宿主 Activity 中實現。當 Activity 通過接口接收了回調,它可以與其他 Fragment 共享信息(When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary)。
舉例來說,一個新聞應用的 Activity 包含兩個 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); }
...
}
接著,需要在 Activity 中實現 OnArticleSelectedListener 接口,並重寫 onArticleSelected() 方法,並通知 Fragment B 來自於 Fragment A 的點擊事件。為了保證宿主 Activity 實現該接口,需在 Fragment A 中的回調方法 onAttach() 中做如下工作(當 Fragment 添加至 Activity 時,系統回調 onAttach() 方法):
若宿主 Activity 未實現 OnArticleSelectedListener 接口,Fragment 將拋出 ClassCastException 異常。正常情況下,Fragment A 的成員變量 mListener 持有實現了 OnArticleSelectedListener 接口的對象引用,所以,Fragment A 可以通過宿主 Activity 傳遞點擊事件。具體來說,當用戶點擊了某個列表項時,Fragment A 中的 onListItemClick() 方法被回調,在該方法中調用 OnArticleSelectedListener 接口中的 onArticleSelected() 方法(程序執行的實際上是重寫的 onArticleSelected() 方法)。
在 Fragment 中回調 onCreateOptionsMenu() 方法可以創建菜單項,為了保證這個方法能夠被系統回調,您必須在 onCreate() 生命周期方法中調用 setHasOptionsMenu() 方法。
當 menu 項被點擊時,Fragment 也像 Activity 一樣,通過 onOptionsItemSelected() 回調方法響應 menu 項的點擊事件。
與Activity類似,Fragment也同樣有三種狀態:
Resumed:在一個處於 running 狀態的 Activity 中,Fragment 處於可見狀態(The fragment is visible in the running activity);
Paused:另一個 Activity 處於前臺並獲得了焦點,該 Fragment 的宿主 Activity 並未被全部遮擋;
Stopped:Fragment 不可見。可能由於宿主 Activity 已不可見或 Fragment已被 Activity 移除(但加入到了Fragment後退棧中),一個處於 stopped 狀態的 Fragment 仍是 alive 的,但當宿主 Activity 被 kill 時,該 Fragment也 將被 kill。
與 Activity 類似,您同樣可以使用 Bundle 對象保存 fragment 的狀態信息,以防宿主 Activity 所在進程被 kill、而迫使 Activity 重新創建、接著 Fragment 才能重新創建、這時 Fragment 的狀態信息將丟失。
重寫 Fragment 的 onSaveInstanceState() 回調方法,並在回傳的 Bundle 參數中保存狀態信息;接著在 onCreate(), onCreateView(), onActivityCreated() 方法中獲得保存的狀態信息。
Activity 與 Fragment 生命周期的一個最大區別就是它們的實例是如何存儲在各自的後退棧中的( how one is stored in its respective back stack):
在默認情況下,Activity 實例會在 stop 狀態時被壓入由系統管理的 Activity 後退棧中(An activity is placed into a back stack of activities that’s managed by the system when it’s stopped, by default),所以用戶可以通過點擊back按鈕恢復已入棧的 Activity 實例。
對於fragment,只能在操作事務時顯式地調用 addToBackStack() 方法將 fragment 壓入由宿主Activity 管理的 fragment 後退棧。
除此之外,Fragment 與 Activity 的生命周期非常相似,您只需了解 Activity 的生命周期是如何影響 Fragment 的生命周期就行了。
!請注意:如需在 Fragment 中使用 Context 對象,您可以調用 getActivity() 方法,但這個方法只能是在 Fragment 已經依附於 Activity 後才能調用( to call getActivity() only when the fragment is attached to an activity)。當 Fragment 未依附於某個 Activity 或 Fragment 已經處於其生命周期的末尾而不再依附於某個 Activity 時,調用 getActivity() 方法會直接返回 null。
宿主 Activity 的生命周期直接影響其管轄的 fragment 的生命周期,Activity 的每一個生命周期方法被回調後,其管轄的 fragment 的相應生命周期方法會跟著回調。如當 Activity 回調 onPause() 時,fragment 也會回調 onPause()。
fragment的其他生命周期方法如下:
onAttach():當 fragment 實例依附於 Activity 時被回調,Activity 對象的引用回傳到該方法中(the Activity is passed in here);
onCreateView():為 fragment 綁定UI視圖時,該方法被回調;
onActivityCreated():當宿主 Activity 的 onCreate() 方法返回後,該方法被回調;
onDestroyView():當與 fragment 綁定的UI視圖被移除時,該方法被回調;
onDetach():當 fragment 不再依附於 Activity 時,該方法被回調;
除了上述方法外,其他的 fragment 生命周期方法均由其宿主 Activity 的生命周期直接影響。有些 Activity 的生命周期方法直接影響了多個 fragment 的生命周期方法,比如說,當 Activity 的 onCreate() 被回調時,將導致 fragment 的 onAttach()、onCreate()、onCreateView()、onActivityCreate() 被連續回調( you can see how each successive state of the activity determines which callback methods a fragment may receive)。如上圖所示。
一旦 Activity 處於 resume 狀態時,您可以自由地添加或移除 fragment,也就是說,只有當 Activity 的狀態為 resume 時,fragment 才能夠自由地控制自己的生命周期。
當 Activity 不在 resume 狀態時,fragment 的生命周期將由宿主 Activity 控制。
如果你有好的技術文章想和大家分享,歡迎向我的公眾號投稿,投稿具體細節請在公眾號主頁點擊「投稿」菜單查看。
歡迎長按下圖 -> 識別圖中二維碼或者掃一掃關注我的公眾號: