Fragment表示Activity中的行為或用戶界面部分,我們可以將多個Fragment組合在一個Activity中來創建多窗格UI,以及在多個Activity中復用同一個Fragment??梢詫ragment視為Activity的模塊化組成部分,它擁有自己的生命周期,能夠處理輸入事件,可以在Activity運行時動態添加、移除Fragment。
Fragment必須始終嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影響。不過,當 Activity 正在運行(處于已恢復 生命周期狀態)時,我們可以獨立操縱每個Fragment,如添加或移除它們。 當執行此類Fragment事務(FragmentTransaction)時,我們也可以將其添加到由 Activity 管理的返回棧 — Activity 中的每個返回棧條目都是一條已發生Fragment事務的記錄。 返回棧讓用戶可以通過按 返回 按鈕撤消Fragment事務(后退)。
當我們將Fragment作為 Activity 布局的一部分添加時,它存在于 Activity 視圖層次結構的某個 ViewGroup 內部,并且Fragment會定義其自己的視圖布局??梢酝ㄟ^在 Activity 的布局文件中聲明Fragment,將其作為 <fragment> 元素插入 Activity 布局中,或者通過將其添加到某個現有 ViewGroup,利用Java代碼進行插入。不過,Fragment并非必須成為 Activity 布局的一部分;還可以將沒有自己 UI 的Fragment用作 Activity 的不可見工作線程。
Fragment的設計原因:
Android 在 Android 3.0(API 級別 11)中引入了Fragment,主要是為了給大屏幕(如平板電腦)上更加動態和靈活的 UI 設計提供支持。由于平板電腦的屏幕比手機屏幕大得多,因此可用于組合和交換 UI 組件的空間更大。利用Fragment實現此類設計時,我們無需管理對視圖層次結構的復雜更改。 通過將 Activity 布局分成Fragment,我們可以在運行時修改 Activity 的外觀,并在由 Activity 管理的返回棧中保留這些更改。
我們應該將每個Fragment 都設計為可重復使用的模塊化 Activity 組件。也就是說,由于每個Fragment都會通過各自的生命周期回調來定義其自己的布局和行為,可以將一個片段加入多個 Activity,因此,應該采用可復用式設計,避免直接從某個Fragment直接操縱另一個Fragment。 這特別重要,因為模塊化Fragment讓我們可以通過更改Fragment的組合方式來適應不同的屏幕尺寸。 在設計可同時支持平板電腦和手機的應用時,可以在不同的布局配置中重復使用您的Fragment,以根據可用的屏幕空間優化用戶體驗。
Fragment的生命周期:
上圖摘自官網,可以看到和Activity的生命周期很像,不過,片段還有幾個額外的生命周期回調,用于處理與 Activity 的唯一交互,以執行構建和銷毀片段 UI 等操作。 這些額外的回調方法是:
onAttach() 在Fragment已與 Activity 關聯時調用(Activity 傳遞到此方法內)。
onCreateView() 調用它可創建與Fragment關聯的視圖層次結構。
onActivityCreated() 在 Activity 的 onCreate() 方法已返回時調用。
onDestroyView() 在移除與Fragment關聯的視圖層次結構時調用。
onDetach() 在取消Fragment與 Activity 的關聯時調用。
創建Fragment:
要想創建Fragment,我們必須創建 Fragment 的子類(或已有其子類),
通常,我們至少應實現以下生命周期方法:
onCreate()
系統會在創建Fragment 時調用此方法。您應該在實現內初始化您想在Fragment 暫停或停止后恢復時保留的必需Fragment 組件。onCreateView()
系統會在Fragment首次繪制其用戶界面時調用此方法。 要想為您的Fragment繪制 UI,您從此方法中返回的 View 必須是Fragment 布局的根視圖。如果Fragment 未提供 UI,您可以返回 null。onPause()
系統將此方法作為用戶離開Fragment 的第一個信號(但并不總是意味著此Fragment 會被銷毀)進行調用。 您通常應該在此方法內確認在當前用戶會話結束后仍然有效的任何更改(因為用戶可能不會返回)。
大多數應用都應該至少為每個Fragment 實現這三個方法,但您還應該使用幾種其他回調方法來處理Fragment 生命周期的各個階段。
通常我們可能會繼承一些系統現有的Fragment 子類,比如:
DialogFragment
顯示浮動對話框。使用此類創建對話框可有效地替代使用 Activity 類中的對話框幫助程序方法,因為您可以將Fragment 對話框納入由 Activity 管理的Fragment 返回棧,從而使用戶能夠返回清除的Fragment。
ListFragment
顯示由適配器(如 SimpleCursorAdapter)管理的一系列項目,類似于 ListActivity。它提供了幾種管理列表視圖的方法,如用于處理點擊事件的 onListItemClick() 回調。
PreferenceFragment
以列表形式顯示 Preference 對象的層次結構,類似于 PreferenceActivity。這在為您的應用創建“設置” Activity 時很有用處。
添加用戶界面:
Fragment通常用作 Activity 用戶界面的一部分,將其自己的布局融入 Activity。
要想為Fragment提供布局,必須實現 onCreateView() 回調方法,Android 系統會在Fragment需要繪制其布局時調用該方法。對此方法的實現返回的 View 必須是Fragment布局的根視圖。
注:如果我們的Fragment是 ListFragment 的子類,則默認實現會從 onCreateView() 返回一個 ListView,因此我們無需實現它。
例如:
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 布局將插入到的父 ViewGroup(來自 Activity 的布局)。savedInstanceState 參數是在恢復片段時,提供上一片段實例相關數據的 Bundle(處理片段生命周期部分對恢復狀態做了詳細闡述)。
inflate() 方法帶有三個參數:
想要擴展的布局的資源 ID;
將作為擴展布局父項的 ViewGroup。傳遞 container 對系統向擴展布局的根視圖(由其所屬的父視圖指定)應用布局參數具有重要意義;
指示是否應該在擴展期間將擴展布局附加至 ViewGroup(第二個參數)的布爾值。(在本例中,其值為 false,因為系統已經將擴展布局插入 container — 傳遞 true 值會在最終布局中創建一個多余的視圖組。)
將Fragment添加到Activity:
有兩種方法將Fragment添加到Activity中。
(1)在Activity的XML布局中把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,并為每個Fragment調用 onCreateView() 方法,以檢索每個Fragment的布局。系統會直接插入Fragment返回的 View 來替代 <fragment> 元素。
注:每個Fragment都需要一個唯一的標識符,重啟 Activity 時,系統可以使用該標識符來恢復Fragment(我們也可以使用該標識符來捕獲Fragment以執行某些事務,如將其移除)。 可以通過三種方式為片段提供 ID:
為 android:id 屬性提供唯一 ID。
為 android:tag 屬性提供唯一字符串。
如果未給以上兩個屬性提供值,系統會使用容器視圖的 ID。
(2)通過Java代碼動態添加到某個現有的ViewGroup
需要確定添加到哪個ViewGroup,然后使用 FragmentTransaction 中的 API來完成對Fragment的操作。例如
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
注意,如果Fragment使用的是v4包的,那么在獲取FragmentManger時要使用getSupportFragmentManager()。
執行Fragment事務
在 Activity 中使用Fragment的一大優點是,可以根據用戶行為通過它們執行添加、移除、替換以及其他操作。 提交給 Activity 的每組更改都稱為事務,可以使用 FragmentTransaction 中的 API 來執行一項事務。也可以將每個事務保存到由 Activity 管理的返回棧內,從而讓用戶能夠回退Fragment更改(類似于回退 Activity)。
每個事務都是我們想要同時執行的一組更改??梢允褂?add()、remove() 和 replace() 等方法為給定事務設置我們想要執行的所有更改。然后,要想將事務應用到 Activity,必須調用 commit()。
不過,在調用 commit() 之前,我們可能想調用 addToBackStack(),以將事務添加到Fragment事務返回棧。 該返回棧由 Activity 管理,允許用戶通過按返回按鈕返回上一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 ID 所標識的布局容器中的任何Fragment(如有)。通過調用 addToBackStack() 可將替換事務保存到返回棧,以便用戶能夠通過按返回按鈕撤消事務并回退到上一Fragment。
如果我們向事務添加了多個更改(如又一個 add() 或 remove()),并且調用了 addToBackStack(),則在調用 commit() 前應用的所有更改都將作為單一事務添加到返回棧,并且返回按鈕會將它們一并撤消。
向 FragmentTransaction 添加更改的順序無關緊要,不過:
必須最后調用 commit()
如果要向同一容器添加多個Fragment,則添加Fragment的順序將決定它們在視圖層次結構中的出現順序
如果沒有在執行移除Fragment的事務時調用addToBackStack() 方法,則事務提交時該Fragment將會被銷毀,用戶也無法回退到該Fragment。不過,如果在刪除Fragment時調用了 addToBackStack(),則系統會停止該Fragment,并在用戶回退時將其恢復。
注:對于每個Fragment事務,都可以通過在commit前調用 setTransition() 來應用過渡動畫。
調用commit方法后不會立即執行事務,而是在UI線程可以執行該操作時再安排其在線程上執行,不過如有必要,可以在UI線程上調用 executePendingTransactions() 來立即執行commit提交的事務,通常不必這樣做,除非其他線程中的作業依賴該事務。
注意:只能在用戶離開 Activity之前使用 commit() 提交事務。如果在該時間點后提交,則會引發異常。 這是因為如需恢復 Activity,則提交后的狀態可能會丟失。 對于丟失提交無關緊要的情況,請使用 commitAllowingStateLoss()。
與Activity的通信
(1)獲取實例
在Fragment中可以通過getActivity()方法來獲得宿主Activity的實例,Activity可以通過 findFragmentById() 或 findFragmentByTag(),通過從 FragmentManager 獲取對 Fragment 的引用來調用片段中的方法。
(2)傳遞數據
Activity向Fragment傳遞數據可以通過fragment.setArguments(Bundle bundle)來傳遞數據,而從Fragment向Activity傳遞數據可以通過回調接口的方法,即在Fragment中聲明一個接口,然后宿主Activity實現這個接口。例如
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");
}
}
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
然后Activity實現該接口。
添加選項菜單
Fragment可以通過 onCreateOptionsMenu() 向Activity的選項菜單添加菜單項,不過為了此方法能夠收到調用,必須在onCreate期間調用 setHasOptionsMenu(),以指示Fragment想要向選項菜單添加菜單項。之后從Fragment添加到選項菜單的菜單項都會被添加到現有菜單項之后,點擊菜單項時,Fragment還會收到 onOptionsItemSelected()的回調。
還可以通過調用 registerForContextMenu(),在Fragment布局中注冊一個視圖來提供上下文菜單。用戶打開上下文菜單時,Fragment會收到對onCreateContextMenu() 的調用。當用戶選擇某個菜單項時,Fragment會收到對 onContextItemSelected() 的調用。
注:盡管Fragment會收到與其添加的每個菜單項對應的菜單項選定回調,但當用戶點擊菜單項時,Activity 會首先收到相應的回調。 如果 Activity 對菜單項選定回調的實現不會處理選定的菜單項,則系統會將事件傳遞到Fragment的回調。 這適用于選項菜單和上下文菜單。