Fragment簡介
Fragment 表示 Activity 中的行為或用戶界面部分。您可以將多個Fragment組合在一個 Activity 中來構建多窗格 UI,以及在多個 Activity 中重復使用某個Fragment。您可以將Fragment視為Activity 的模塊化組成部分,它具有自己的生命周期,能接收自己的輸入事件,並且您可以在 Activity 運行時添加或刪除Fragment(有點像您可以在不同 Activity 中重復使用的“子Activity”)。
Fragment必須始終嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影響。 例如,當 Activity 暫停時,其中的所有Fragment也會暫停;當 Activity 被銷毀時,所有Fragment也會被銷毀。 不過,當 Activity 正在運行(處於已恢復生命周期狀態)時,您可以獨立操縱每個Fragment,如添加或移除它們。當您執行此類Fragment事務時,您也可以將其添加到由 Activity 管理的返回棧—Activity 中的每個返回棧條目都是一條已發生Fragment事務的記錄。 返回棧讓用戶可以通過按“返回”按鈕撤消Fragment事務(後退)。
當您將Fragment作為 Activity 布局的一部分添加時,它存在於 Activity 視圖層次結構的某個 ViewGroup 內部,並且Fragment會定義其自己的視圖布局。您可以通過在 Activity 的布局文件中聲明Fragment,將其作為 fragment元素插入您的 Activity 布局中,或者通過將其添加到某個現有 ViewGroup,利用應用代碼進行插入。不過,Fragment並非必須成為 Activity布局的一部分;您還可以將沒有自己 UI 的Fragment用作 Activity 的不可見工作線程。
本文描述如何在開發您的應用時使用Fragment,包括將Fragment添加到 Activity 返回棧時如何保持其狀態、如何與 Activity 及 Activity 中的其他Fragment共享事件、如何為 Activity 的操作欄發揮作用等等。
設計原理
Android 在 Android 3.0(API 11 級)中引入了Fragment,主要是為了給大屏幕(如平板電腦)上更加動態和靈活的 UI 設計提供支持。由於平板電腦的屏幕比手機屏幕大得多,因此可用於組合和交換 UI 組件的空間更大。利用片段實現此類設計時,您無需管理對視圖層次結構的復雜更改。通過將 Activity 布局分成Fragment,您可以在運行時修改 Activity 的外觀,並在由 Activity 管理的返回棧中保留這些更改。
例如,新聞應用可以使用一個Fragment在左側顯示文章列表,使用另一個Fragment在右側顯示文章—兩個片段並排顯示在一個 Activity 中,每個Fragment都具有自己的一套生命周期回調方法,並各自處理自己的用戶輸入事件。 因此,用戶不需要使用一個 Activity 來選擇文章,然後使用另一個 Activity 來閱讀文章,而是可以在同一個 Activity 內選擇文章並進行閱讀,如圖 1 中的平板電腦布局所示。
您應該將每個Fragment都設計為可重復使用的模塊化 Activity 組件。也就是說,由於每個Fragment都會通過各自的生命周期回調來定義其自己的布局和行為,您可以將一個Fragment加入多個Activity,因此,您應該采用可復用式設計,避免直接從某個Fragment直接操縱另一個Fragment。 這特別重要,因為模塊化Fragment讓您可以通過更改Fragment的組合方式來適應不同的屏幕尺寸。在設計可同時支持平板電腦和手機的應用時,您可以在不同的布局配置中重復使用您的Fragment,以根據可用的屏幕空間優化用戶體驗。 例如,在手機上,如果不能在同一Activity內儲存多個Fragment,可能必須利用單獨Fragment來實現單窗格 UI。
圖 1. 有關由Fragment定義的兩個 UI 模塊如何適應不同設計的示例:通過組合成一個 Activity 來適應平板電腦設計,通過單獨Fragment來適應手機設計。
例如—仍然以新聞應用為例—在平板電腦尺寸的設備上運行時,該應用可以在Activity A 中嵌入兩個Fragment。不過,在手機尺寸的屏幕上,沒有足以儲存兩個Fragment的空間,因此Activity A只包括用於顯示文章列表的Fragment,當用戶選擇文章時,它會啟動Activity B,其中包括用於閱讀文章的第二個Fragment。因此,應用可通過重復使用不同組合的Fragment來同時支持平板電腦和手機,如圖 1 所示。
創建Fragment
要想創建Fragment,您必須創建 Fragment 的子類(或已有其子類)。Fragment 類的代碼與 Activity 非常相似。它包含與 Activity 類似的回調方法,如 onCreate()、onStart()、onPause() 和 onStop()。實際上,如果您要將現有 Android 應用轉換為用Fragment,可能只需將代碼從 Activity 的回調方法移入Fragment相應的回調方法中。
通常,您至少應實現以下生命周期方法:
(1)
onCreate()
系統會在創建Fragment時調用此方法。您應該在實現內初始化您想在Fragment暫停或停止後恢復時保留的必需Fragment組件。
(2)
onCreateView()
系統會在Fragment首次繪制其用戶界面時調用此方法。 要想為您的Fragment繪制 UI,您從此方法中返回的 View 必須是Fragment布局的根視圖。如果Fragment未提供 UI,您可以返回 null。
(3)
onPause()
系統將此方法作為用戶離開Fragment的第一個信號(但並不總是意味著此片段會被銷毀)進行調用。 您通常應該在此方法內確認在當前用戶會話結束後仍然有效的任何更改(因為用戶可能不會返回)。
大多數應用都應該至少為每個Fragment實現這三個方法,但您還應該使用幾種其他回調方法來處理Fragment生命周期的各個階段。 處理片段生命周期部分對所有生命周期回調方法做了更詳盡的闡述。
圖 2. Fragmen的生命周期(其 Activity 運行時)
您可能還想擴展幾個子類,而不是 Fragment 基類:
(1)
DialogFragment
顯示浮動對話框。使用此類創建對話框可有效地替代使用 Activity 類中的對話框幫助程序方法,因為您可以將Fragment對話框納入由 Activity 管理的Fragment返回棧,從而使用戶能夠返回清除的Fragment。
(2)
ListFragment
顯示由適配器(如 SimpleCursorAdapter)管理的一系列項目,類似於 ListActivity。它提供了幾種管理列表視圖的方法,如用於處理點擊事件的 onListItemClick() 回調。
(3)
PreferenceFragment
以列表形式顯示 Preference 對象的層次結構,類似於 PreferenceActivity。這在為您的應用創建“設置” Activity 時很有用處。
1.添加用戶界面
Fragment通常用作 Activity 用戶界面的一部分,將其自己的布局融入 Activity。
要想為Fragment提供布局,您必須實現 onCreateView() 回調方法,Android 系統會在片段需要繪制其布局時調用該方法。您對此方法的實現返回的 View 必須是Fragment布局的根視圖。
注:如果您的Fragment是 ListFragment 的子類,則默認實現會從 onCreateView() 返回一個 ListView,因此您無需實現它。
要想從 onCreateView() 返回布局,您可以通過 XML 中定義的布局資源來擴展布局。為幫助您執行此操作,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 參數是您的Fragment布局將插入到的父 ViewGroup(來自 Activity 的布局)。savedInstanceState 參數是在恢復Fragment時,提供上一片段實例相關數據的 Bundle(處理Fragment生命周期部分對恢復狀態做了詳細闡述)。
inflate() 方法帶有三個參數:
(1)您想要擴展的布局的資源 ID;
(2)將作為擴展布局父項的 ViewGroup。傳遞 container 對系統向擴展布局的根視圖(由其所屬的父視圖指定)應用布局參數具有重要意義;
(3)指示是否應該在擴展期間將擴展布局附加至 ViewGroup(第二個參數)的布爾值。(在本例中,其值為 false,因為系統已經將擴展布局插入 container—傳遞 true 值會在最終布局中創建一個多余的視圖組。)
現在,您已經了解了如何創建提供布局的Fragment。接下來,您需要將該Fragment添加到您的 Activity 中。
2.向 Activity 添加Fragment
通常,Fragment向宿主 Activity 貢獻一部分 UI,作為 Activity 總體視圖層次結構的一部分嵌入到 Activity 中。可以通過兩種方式向 Activity 布局添加Fragment:
(1)在 Activity 的布局文件內聲明Fragment
在本例中,您可以將Fragment當作視圖來為其指定布局屬性。 例如,以下是一個具有兩個Fragment的 Activity 的布局文件:
fragment中的 android:name 屬性指定要在布局中實例化的 Fragment 類。
當系統創建此 Activity 布局時,會實例化在布局中指定的每個Fragment,並為每個Fragment調用 onCreateView() 方法,以檢索每個Fragment的布局。系統會直接插入Fragment返回的 View 來替代fragment元素。
注:每個Fragment都需要一個唯一的標識符,重啟 Activity 時,系統可以使用該標識符來恢復Fragment(您也可以使用該標識符來捕獲Fragment以執行某些事務,如將其刪除)。
可以通過三種方式為Fragment提供 ID:
1)為 android:id 屬性提供唯一 ID
2)為 android:tag 屬性提供唯一字符串
3)如果您未給以上兩個屬性提供值,系統會使用容器視圖的 ID
(2)或者通過編程方式將Fragment添加到某個現有 ViewGroup
您可以在 Activity 運行期間隨時將Fragment添加到 Activity 布局中。您只需指定要將Fragment放入哪個 ViewGroup。要想在您的 Activity 中執行Fragment事務(如添加、刪除或替換Fragment),您必須使用 FragmentTransaction 中的 API。您可以像下面這樣從 Activity 獲取一個 FragmentTransaction實例:
FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
然後,您可以使用 add() 方法添加一個Fragment,指定要添加的Fragment以及將其插入哪個視圖。例如:
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
傳遞到 add() 的第一個參數是 ViewGroup,即應該放置片段的位置,由資源 ID 指定,第二個參數是要添加的Fragment。
一旦您通過 FragmentTransaction 做出了更改,就必須調用 commit() 以使更改生效。
3.添加沒有 UI 的Fragment
上例展示了如何向您的 Activity 添加Fragment以提供 UI。不過,您還可以使用Fragment為 Activity 提供後台行為,而不顯示額外 UI。
要想添加沒有 UI 的Fragment,請使用add(Fragment, String) 從 Activity 添加Fragment(為片段提供一個唯一的字符串“標記”,而不是視圖 ID)。這會添加Fragment,但由於它並不與Activity 布局中的視圖關聯,因此不會收到對 onCreateView() 的調用。因此,您不需要實現該方法。
並非只能為非 UI Fragment提供字符串標記—您也可以為具有 UI 的Fragment提供字符串標記—但如果Fragment沒有 UI,則字符串標記將是標識它的唯一方式。如果您想稍後從 Activity中獲取Fragment,則需要使用 findFragmentByTag()。
如需查看將沒有 UI 的Fragment用作後台工作線程的示例 Activity,請參閱 FragmentRetainInstance.java 示例,該示例包括在 SDK 示例(通過 Android SDK 管理器提供)中,以
/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java 形式位於您的系統中。
管理Fragment
要想管理您的 Activity 中的Fragment,您需要使用 FragmentManager。要想獲取它,請從您的 Activity 調用getFragmentManager()。
您可以使用 FragmentManager 執行的操作包括:
(1)通過 findFragmentById()(對於在 Activity 布局中提供 UI 的Fragment)或 findFragmentByTag()(對於提供或不提供 UI 的Fragment)獲取 Activity 中存在的Fragment
(2)通過 popBackStack()(模擬用戶發出的 Back 命令)將Fragment從返回棧中彈出
(3)通過 addOnBackStackChangedListener() 注冊一個偵聽返回棧變化的偵聽器
如上文所示,您也可以使用 FragmentManager 打開一個 FragmentTransaction,通過它來執行某些事務,如添加和刪除片段。
執行Fragment事務
在 Activity 中使用Fragment的一大優點是,可以根據用戶行為通過它們執行添加、刪除、替換以及其他操作。 您提交給 Activity 的每組更改都稱為事務,您可以使用FragmentTransaction 中的 API 來執行一項事務。您也可以將每個事務保存到由 Activity 管理的返回棧內,從而讓用戶能夠回退Fragment更改(類似於回退 Activity)。
您可以像下面這樣從 FragmentManager 獲取一個 FragmentTransaction 實例:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每個事務都是您想要同時執行的一組更改。您可以使用 add()、remove() 和 replace() 等方法為給定事務設置您想要執行的所有更改。然後,要想將事務應用到 Activity,您必須調用 commit()。
不過,在您調用 commit() 之前,您可能想調用 addToBackStack(),以將事務添加到片段事務返回棧。 該返回棧由 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() 可將替換事務保存到返回棧,以便用戶能夠通過按“返回” 按鈕撤消事務並回退到上一片段。
如果您向事務添加了多個更改(如又一個 add() 或 remove()),並且調用了 addToBackStack(),則在調用 commit() 前應用的所有更改都將作為單一事務添加到返回棧,並且“返回” 按鈕會將它們一並撤消。
向 FragmentTransaction 添加更改的順序無關緊要,不過:
(1)您必須最後調用 commit()
(2)如果您要向同一容器添加多個Fragment,則您添加片段的順序將決定它們在視圖層次結構中的出現順序
如果您沒有在執行刪除Fragment的事務時調用 addToBackStack(),則事務提交時該Fragment會被銷毀,用戶將無法回退到該Fragment。 不過,如果您在刪除Fragment時調用了 addToBackStack(),則系統會停止該Fragment,並在用戶回退時將其恢復。
提示:對於每個Fragment事務,您都可以通過在提交前調用 setTransition() 來應用過渡動畫。
調用 commit() 不會立即執行事務,而是在 Activity 的 UI 線程(“主”線程)可以執行該操作時再安排其在線程上運行。不過,如有必要,您也可以從 UI 線程調用executePendingTransactions() 以立即執行 commit() 提交的事務。通常不必這樣做,除非其他線程中的作業依賴該事務。
注意:您只能在 Activity保存其狀態(用戶離開 Activity)之前使用 commit() 提交事務。如果您試圖在該時間點後提交,則會引發異常。這是因為如需恢復 Activity,則提交後的狀態可能會丟失。對於丟失提交無關緊要的情況,請使用commitAllowingStateLoss()。
與 Activity 通信
盡管 Fragment 是作為獨立於 Activity 的對象實現,並且可在多個 Activity 內使用,但Fragment的給定實例會直接綁定到包含它的 Activity。
具體地說,Fragment可以通過 getActivity() 訪問 Activity 實例,並輕松地執行在 Activity 布局中查找視圖等任務。
View listView = getActivity().findViewById(R.id.list);
同樣地,您的 Activity 也可以使用 findFragmentById() 或 findFragmentByTag(),通過從 FragmentManager 獲取對 Fragment 的引用來調用片段中的方法。例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
1.創建對 Activity 的事件回調
在某些情況下,您可能需要通過Fragment與 Activity 共享事件。執行此操作的一個好方法是,在Fragment內定義一個回調接口,並要求宿主 Activity 實現它。 當 Activity 通過該接口收到回調時,可以根據需要與布局中的其他Fragment共享這些信息。
例如,如果一個新聞應用的 Activity 有兩個片段 —一個用於顯示文章列表(Fragment A),另一個用於顯示文章(FragmentB)—,那麼Fragment A必須在列表項被選定後告知 Activity,以便它告知Fragment B 顯示該文章。 在本例中,OnArticleSelectedListener 接口在Fragment A 內聲明:
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
然後,該Fragment的宿主 Activity 會實現 OnArticleSelectedListener 接口並替代 onArticleSelected(),將來自FragmentA 的事件通知Fragment B。為確保宿主 Activity 實現此界面,Fragment A 的 onAttach() 回調方法(系統在向 Activity 添加Fragment時調用的方法)會通過轉換傳遞到 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會引發 ClassCastException。實現時,mListener 成員會保留對 Activity 的OnArticleSelectedListener 實現的引用,以便Fragment A 可以通過調用 OnArticleSelectedListener 界面定義的方法與 Activity 共享事件。例如,如果Fragment A 是 ListFragment 的一個擴展,則用戶每次點擊列表項時,系統都會調用Fragment中的onListItemClick(),然後該方法會調用 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 獲取文章的 ID。
2.向操作欄添加項目
您的Fragment可以通過實現onCreateOptionsMenu() 向 Activity 的選項菜單(並因此向操作欄)貢獻菜單項。不過,為了使此方法能夠收到調用,您必須在 onCreate() 期間調用setHasOptionsMenu(),以指示Fragment想要向選項菜單添加菜單項(否則,Fragment將不會收到對 onCreateOptionsMenu() 的調用)。
注:盡管您的Fragment會收到與其添加的每個菜單項對應的菜單項選定回調,但當用戶選擇菜單項時,Activity 會首先收到相應的回調。 如果 Activity 對菜單項選定回調的實現不會處理選定的菜單項,則系統會將事件傳遞到片段的回調。 這適用於選項菜單和上下文菜單。
處理Fragment生命周期
管理Fragment生命周期與管理 Activity 生命周期很相似。和 Activity 一樣,片段也以三種狀態存在:
(1)恢復
Fragment在運行中的 Activity 中可見。
(2)暫停
另一個 Activity 位於前台並具有焦點,但此Fragment所在的 Activity 仍然可見(前台 Activity 部分透明,或未覆蓋整個屏幕)。
(3)停止
Fragment不可見。宿主 Activity 已停止,或Fragment已從 Activity 中刪除,但已添加到返回棧。 停止Fragment仍然處於活動狀態(系統會保留所有狀態和成員信息)。 不過,它對用戶不再可見,如果 Activity 被終止,它也會被終止。
同樣與 Activity 一樣,假使 Activity 的進程被終止,而您需要在重建 Activity 時恢復Fragment狀態,您也可以使用 Bundle 保留片段的狀態。您可以在Fragment的onSaveInstanceState() 回調期間保存狀態,並可在 onCreate()、onCreateView() 或 onActivityCreated() 期間恢復狀態。如需了解有關保存狀態的詳細信息,請參閱Android Activity基礎詳解。
Activity 生命周期與Fragment生命周期之間的最顯著差異在於它們在其各自返回棧中的存儲方式。 默認情況下,Activity 停止時會被放入由系統管理的 Activity 返回棧(以便用戶通過“返回” 按鈕回退到Activity,任務和返回棧對此做了闡述)。不過,僅當您在刪Fragment的事務執行期間通過調用 addToBackStack() 顯式請求保存實例時,系統才會將片段放入由宿主 Activity 管理的返回棧。
在其他方面,管理Fragment生命周期與管理 Activity 生命周期非常相似。 因此,管理 Activity 生命周期的做法同樣適用Fragment。 但您還需要了解 Activity 的生命周期對Fragment生命周期的影響。
注意:如需 Fragment 內的某個 Context 對象,可以調用 getActivity()。但要注意,請僅在片段附加到 Activity 時調用 getActivity()。如果Fragment尚未附加,或在其生命周期結束期間分離,則 getActivity() 將返回 null。
圖 3. Activity 生命周期對Fragmnet生命周期的影響
1.與 Activity 生命周期協調一致
Fragment所在的 Activity 的生命周期會影響Fragment的生命周期,其表現為,Activity 的每次生命周期回調都會引發每個Fragment的類似回調。 例如,當 Activity 收到 onPause() 時,Activity 中的每個Fragment也會收到 onPause()。
不過,Fragment還有幾個額外的生命周期回調,用於處理與 Activity 的唯一交互,以執行構建和銷毀Fragment UI 等操作。這些額外的回調方法是:
(1)onAttach()
在Fragment已與 Activity 關聯時調用(Activity 傳遞到此方法內)。
(2)onCreateView()
調用它可創建與Fragment關聯的視圖層次結構。
(3)onActivityCreated()
在 Activity 的 onCreate() 方法已返回時調用。
(4)onDestroyView()
在刪除與Fragment關聯的視圖層次結構時調用。
(5)onDetach()
在取消Fragment與 Activity 的關聯時調用。
圖 3 圖示說明了受其宿主 Activity 影響的Fragment生命周期流。在該圖中,您可以看到 Activity 的每個連續狀態如何決定Fragment可以收到的回調方法。 例如,當 Activity 收到其onCreate() 回調時,Activity 中的Fragment只會收到 onActivityCreated() 回調。
一旦 Activity 達到恢復狀態,您就可以意向 Activity 添加Fragment和刪除其中的Fragment。 因此,只有當 Activity 處於恢復狀態時,Fragment的生命周期才能獨立變化。
不過,當 Activity 離開恢復狀態時,Fragment會在 Activity 的推動下再次經歷其生命周期。
示例
為了將本文闡述的所有內容融會貫通,以下提供了一個示例,其中的 Activity 使用兩個Fragment來創建一個雙窗格布局。 下面的 Activity 包括兩個Fragment:一個用於顯示莎士比亞戲劇標題列表,另一個用於從列表中選定戲劇時顯示其摘要。 此外,它還展示了如何根據屏幕配置提供不同的Fragment配置。
主 Activity 會在 onCreate() 期間以常規方式應用布局:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_layout);
}
應用的布局為 fragment_layout.xml:
<framelayout android:background="?android:attr/detailsElementBackground" android:id="@+id/details" android:layout_height="match_parent" android:layout_weight="1" android:layout_width="0px">
</framelayout>
通過使用此布局,系統可在 Activity 加載布局時立即實例化 TitlesFragment(列出戲劇標題),而 FrameLayout(用於顯示戲劇摘要的Fragment所在位置)則會占用屏幕右側的空間,但最初處於空白狀態。 正如您將在下文所見的那樣,用戶從列表中選擇某個項目後,系統才會將Fragment放入 FrameLayout。
不過,並非所有屏幕配置都具有足夠的寬度,可以並排顯示戲劇列表和摘要。 因此,以上布局僅用於橫向屏幕配置(布局保存在 res/layout-land/fragment_layout.xml)。
因此,當屏幕縱向顯示時,系統會應用以下布局(保存在 res/layout/fragment_layout.xml):
<framelayout android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
</framelayout>
此布局僅包括 TitlesFragment。這意味著,當設備縱向顯示時,只有戲劇標題列表可見。 因此,當用戶在此配置中點擊某個列表項時,應用會啟動一個新 Activity 來顯示摘要,而不是加載另一個Fragment。
接下來,您可以看到如何在Fragment類中實現此目的。第一個片段是 TitlesFragment,它顯示莎士比亞戲劇標題列表。該Fragment擴展了 ListFragment,並依靠它來處理大多數列表視圖工作。
當您檢查此代碼時,請注意,用戶點擊列表項時可能會出現兩種行為:系統可能會創建並顯示一個新Fragment,從而在同一活動中顯示詳細信息(將Fragment添加到 FrameLayout),也可能會啟動一個新活動(在該活動中可顯示Fragment),具體取決於這兩個布局中哪一個處於活動狀態。
public static class TitlesFragment extends ListFragment {
boolean mDualPane;
int mCurCheckPosition = 0;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Populate list with our static array of titles.
setListAdapter(new ArrayAdapter(getActivity(),
android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
// Check to see if we have a frame in which to embed the details
// fragment directly in the containing UI.
View detailsFrame = getActivity().findViewById(R.id.details);
mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
if (mDualPane) {
// In dual-pane mode, the list view highlights the selected item.
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Make sure our UI is in the correct state.
showDetails(mCurCheckPosition);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
showDetails(position);
}
/**
* Helper function to show the details of a selected item, either by
* displaying a fragment in-place in the current UI, or starting a
* whole new activity in which it is displayed.
*/
void showDetails(int index) {
mCurCheckPosition = index;
if (mDualPane) {
// We can display everything in-place with fragments, so update
// the list to highlight the selected item and show the data.
getListView().setItemChecked(index, true);
// Check what fragment is currently shown, replace if needed.
DetailsFragment details = (DetailsFragment)
getFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {
// Make new fragment to show this selection.
details = DetailsFragment.newInstance(index);
// Execute a transaction, replacing any existing fragment
// with this one inside the frame.
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (index == 0) {
ft.replace(R.id.details, details);
} else {
ft.replace(R.id.a_item, details);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Intent intent = new Intent();
intent.setClass(getActivity(), DetailsActivity.class);
intent.putExtra("index", index);
startActivity(intent);
}
}
}
第二個Fragment DetailsFragment 顯示從 TitlesFragment 的列表中選擇的項目的戲劇摘要:
public static class DetailsFragment extends Fragment {
/**
* Create a new instance of DetailsFragment, initialized to
* show the text at 'index'.
*/
public static DetailsFragment newInstance(int index) {
DetailsFragment f = new DetailsFragment();
// Supply index input as an argument.
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);
return f;
}
public int getShownIndex() {
return getArguments().getInt("index", 0);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container == null) {
// We have different layouts, and in one of them this
// fragment's containing frame doesn't exist. The fragment
// may still be created from its saved state, but there is
// no reason to try to create its view hierarchy because it
// won't be displayed. Note this is not needed -- we could
// just run the code below, where we would create and return
// the view hierarchy; it would just never be used.
return null;
}
ScrollView scroller = new ScrollView(getActivity());
TextView text = new TextView(getActivity());
int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
4, getActivity().getResources().getDisplayMetrics());
text.setPadding(padding, padding, padding, padding);
scroller.addView(text);
text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
return scroller;
}
}
從 TitlesFragment 類中重新調用,如果用戶點擊某個列表項,且當前布局“根本不”包括 R.id.details 視圖(即 DetailsFragment 所屬視圖),則應用會啟動DetailsActivity Activity 以顯示該項目的內容。
以下是 DetailsActivity,它簡單地嵌入了 DetailsFragment,以在屏幕為縱向時顯示所選的戲劇摘要:
public static class DetailsActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE) {
// If the screen is now in landscape mode, we can show the
// dialog in-line with the list so we don't need this activity.
finish();
return;
}
if (savedInstanceState == null) {
// During initial setup, plug in the details fragment.
DetailsFragment details = new DetailsFragment();
details.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
}
}
}
請注意,如果配置為橫向,則此 Activity 會自行完成,以便主 Activity 可以接管並沿 TitlesFragment 顯示 DetailsFragment。如果用戶在縱向顯示時啟動 DetailsActivity,但隨後旋轉為橫向(這會重啟當前 Activity),就可能出現這種情況。