備註:來源參考http://developer.android.com/guide/topics/fundamentals/fragments.html
碎片(Fragments)
一個碎片代表着活動內的一個行爲或者是一個用戶界面的一部分。你可以在一個單獨的活動內組合多個碎片來構建一個多窗口UI或者在多個活動內重用一個碎片。也可以把碎片看做是模塊化的活動,碎片有它自己的生命週期,接受它自己的輸入事件,並且在活動運行行添加或移除它們(有點類似於"子活動",你可以在不同的活動力重用它)。
碎片必須嵌入在一個活動裏,並且他們的生命週期受到宿主活動生命週期的直接影響。例如,當宿主活動暫停時,所有活動內的碎片也處於暫停,當宿主活動被銷燬時,所有的碎片也被銷燬。然而,當宿主活動處於運行狀態時(指它處於一個可被恢復的生命週期狀態),你可以獨立地控制每個碎片,譬如添加或刪除它們。當我們執行此類碎片的事務時,你也可以將碎片添加到一個由宿主活動管理的"後退堆棧"裏。宿主活動內的每一個"後退堆棧"都是碎片已發生過的事務記錄。"後退堆棧"允許用戶通過按下“後退”鍵來倒轉一個碎片的事務(向後瀏覽)。
當添加一個碎片作爲活動佈局的一部分時,它就位於了容納宿主活動視圖層次的視圖組內,並且碎片可以定義自己的視圖佈局。可以通過在活動佈局文件內聲明<fragment>
元素而將其插入到活動佈局內,或者通過代碼將其加入到一個現有的視圖組內。然而,一個碎片不必作爲活動佈局的一部分而存在。你可以把不擁有UI的碎片作爲活動的一個不可見協助者。
這片文章將描述如何在自己的應用中使用碎片,包括碎片在被添加到宿主活動"後退堆棧"時,及與活動和其他的碎片分享事件時,它們是如何維護自己的狀態,以及如何促成活動的動作條,或更多。
設計理念(Design Philosophy)
碎片的概念是由Android 3.0引入的,主要的目的是爲了在大屏幕設備上支持更多動態的和靈活的UI設計,例如在平板電腦上。因爲平板電腦的屏幕比手機的屏幕要大,因而在手機屏幕上沒有足夠的空間來組合和交換UI組件。碎片則允許你如此設計而不必去管理視圖層次的複雜變化。通過將活動佈局分割爲多個碎片,你便可以在運行時改變活動的呈現,並可以把那些變化保存在一個"後退堆棧"裏。
例如,一個應用使用一個碎片在屏幕左測展示文章列表,另一個碎片在屏幕右側展示文章內容,而這兩個碎片並排地出現在一個活動裏,平且每個碎片都有它自己的生命週期回調方法和處理它們各自輸入事件的集合。因此, 用戶能夠在同一個活動內選擇和瀏覽一篇文章,而不必用一個活動選擇一個文章,用另一個活動來展示文章內容,在平板電腦上的佈局如圖(1)所示:
圖(1)
此圖展示了對於一個平板設計來說,由碎片定義的兩個UI模塊是如何被整合到一個活動裏,而在手機上則是分開的。
你可以把碎片設計成一個模塊化的,可重用的活動組件。也就是說,因爲每個碎片定義了它自己的佈局,行爲及生命週期回調函數,因此你可以在多個活動中包括同一個碎片,所以你應該設計可重用的碎片,並避免在一個碎片直接地維護另一個碎片。這一點特別地重要,因爲一個模塊化的碎片允許你改變碎片組合來適應不同尺寸的屏幕。當你在設計同時支持平板電腦和手機設備的應用時,你可以根據不同的佈局配置來重用你的碎片,這樣就可以在可利用的屏幕空間內優化用戶的體驗。例如,以手機設備爲例,當一個活動內一個碎片不能滿足需要時,此時可能需要分離碎片來提供一個單獨的窗口UI。
繼續以圖(1)的應用爲例,當應用在平板電腦大小尺寸的設備上運行時,應用可以包含兩個碎片在活動A中。然而,在手機屏幕大小的設備上,沒有足夠的空間容納兩個碎片,所以,在活動A裏只包含了一個碎片用來顯示文章列表,當用戶選擇一個文章時便會啓動活動B,該活動裏包括第二碎片用來展示文章內容。因此,如圖(1)所示,通過不同的組合來重用碎片可以使得應用同時支持平板和手機兩種設備。
更多關於爲支持不同屏幕配置而爲應用設計不同的碎片組合的信息,詳見Supporting Tablets and Handsets.
創建一個碎片(Creating a Fragment)
爲了創建一個碎片,你必須創建一個Fragment的子類(或是一個已存在的Fragment子類的子類)。Fragment類的代碼看上去像一個Activity。它包括了類似於活動的回調函數,譬如onCreate()
,onStart()
,onPause()
, andonStop()
等。事實上,如果你把一個現有的Android應用轉換成使用碎片的應用,你可能只是簡單地把活動回調方法內的代碼轉移到對應的碎片回調方法內即可。
通常,你應該至少實現以下的生命週期方法:
onCreate
()
當創建碎片時系統調用此方法。在你的實現內部,應該初始化必要的碎片組件,它們是你想在碎片被暫停或停止時,後又要被恢復而保存的組件。
當碎片第一次繪製它的用戶界面時由系統調用該方法。爲了給你的碎片繪製一個UI,你必須在從這個方法裏返回一個視圖(View),這也是你的碎片佈局的根。如果碎片沒有提供一個UI,可以返回NULL。
onPause()
系統調用該方法作爲用戶正在離開碎片(儘管這不常常意味着碎片正在被銷燬)的第一個標誌。該方法常常是你應該提交任何變動的地方,這些變動應該在當前用戶對話之外而被持久地保存下來(因爲用戶可能不再返回到當前對對話)。
絕大多數的應用應該爲每個碎片至少實現這三個方法, 但是還有幾個回調方法可供你使用來處理碎片生命週期的各個階段。所有的回調方法在稍後關於處理碎片生命週期(Handling the Fragment Lifecycle)的章節裏進行介紹。
圖(2)碎片的生命週期(當活動運行時)
這裏幾個可以擴展的子類,而不用以Fragment
爲基類:
顯示爲一浮動的對話框。在活動類內,使用它創建一個對話框是個很好的選擇,因爲你可以把一個碎片對話框
- 併入到碎片的"後退堆棧"裏,進而允許用戶返回到曾被解除過的碎片。
顯示爲一個由適配器(諸如一個SimpleCursorAdapter
)管理的列表項, 類似於ListActivity。它提供了幾個用來管理列表視圖的方法,諸如用onListItemClick()
回調來處理點擊事件。
作爲列表來展示首選對象的層級結構,類似於PreferenceActivity
。當爲你的應用創建"設置"活動時,它十分有用。
添加一個用戶界面(Adding a user interface)
一個碎片常常被用作宿主活動的用戶界面的一部分,因爲它爲宿主活動貢獻出它自己的佈局。
爲給碎片提供一個佈局,你必須實現onCreateView()
回調方法,在碎片繪製自己佈局時用系統調用。onCreateView()
方法的實現必須返回一個View,它是碎片佈局的根。
提醒: 如果你的碎片是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參數是父視圖(來自活動佈局),你的碎片佈局將被
嵌入到這裏。savedInstanceState
參數是一個Bundle
,如果碎片將來被恢復時(在處理碎片生命週期章節有更多的關於恢復狀態的介紹),它提供了關於上一個碎片實例的相關數據。
inflate方法有三個參數:
- 想要擴充的佈局ID。
- 被擴充佈局的父視圖。傳遞container參數是重要的,由它的父視圖指定,目的爲了讓系統把佈局參數應用到被擴展布局的根視圖。(specified by the parent view in which it's going)。
- 一個boolean指明在佈局在被擴充期間是否將其附加到
ViewGroup
(第二個參數)。(這種情況系,參數是false,因爲系統已經把擴充的佈局插入到container
內,而傳遞true將在最終的佈局上創建一個多餘的視圖組)。
現在,你已經看到了如何創建一個提供了佈局的碎片。接下來,你需要把佈局添加到你的活動中。
向活動中添加碎片(Adding a fragment to an activity)
通常,碎片把UI的一部分貢獻給他的宿主活動,這部分UI視作爲宿主活動整體視圖層級結構的一部分被嵌入到活動中。 這裏有兩種方法可以把碎片加入到活動佈局裏。
1. 在活動的佈局文件裏聲明碎片
在這種情況下,你能爲碎片指定佈局屬性,好像它是一個視圖。例如,這裏有一個帶有連個碎片的活動佈局文件。
<?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屬性指定了在佈局裏實例化的碎片類。當系統創建這個活動佈局時,它初始化每一個在佈局內指定的碎片,並分別調用它們的
onCreateView()
方法來取得每個碎片的佈局。然後,系統把由碎片返回的視圖直接插入到<fragment>元素的位置。
提醒:每個碎片需要一個獨一無二的標示符,如果活動被重新啓動時,系統可以通過它來恢復碎片(同時,你也可以使用它來捕獲碎片來執行事務,例如移除它)。這有三種方式可爲碎片提供ID:
- 給android:id屬性提供一個獨一無二的ID.
- 給
android:tag
屬性提供一個獨一無二的字符串標記. - 如果上述兩個都沒提供,系統將使用container參數視圖的ID.
2. 以編程方式向已有的視圖組添加碎片
在活動處於運行的任何時候,你都能向活動佈局裏添加碎片。你只需要簡單地指定一個放置碎片的視圖組即可。
爲了在活動裏執行碎片事務操作(諸如,添加,移除,替換),你必須使用來自FragmentTransaction
的APIs。在你的活動裏,你可以像這樣來獲取一個FragmentTransaction
的實例:
FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
然後,可以使用
add()
方法來添加碎片,需要指定將要添加的碎片及碎片插入的視圖。例如:
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
傳遞給add()
的第一個參數是碎片應該被放置的視圖組,由資源ID指定,第二個參數是將要添加的碎片。一旦通過FragmentTransaction
完成了你的改變,必須調用commit()
以保證改變生效。
添加一個沒有UI的碎片(Adding a fragment without a UI)
上面的例子演示瞭如何爲了提供一個UI而向你的活動中添加碎片。然而,你也可以使用碎片爲活動提供一個後臺行爲而不必呈現出額外的UI。
爲了添加一個沒有UI的碎片,在活動內使用add(Fragment, String)
。它添加了碎片,但是,因爲在活動佈局裏沒有視圖與它有關聯,因此碎片不會收到方法
(爲碎片提供一個獨一無二的字符串“標記”,而不是一個視圖ID)onCreateView()
調用。所以,不必去實現這個方法。
給碎片提供一個字符串標記並不是嚴格爲沒有UI的碎片的,你也可以爲那些有UI的碎片提供字符串標記,但是,如果碎片UI,那麼字符串標記則是唯一辨識它的方法。如果想事後在活動裏獲取碎片,你需要使用findFragmentByTag()
方法。
有個例子,活動使用碎片作爲後臺協助者,它沒有UI,詳見FragmentRetainInstance.java
。
管理碎片(Managing Fragments)
爲在活動裏管理碎片,你需要使用FragmentManager
。爲了得到它,在活動內調用getFragmentManager()
方法。
利用FragmentManager
,你可以做的事情包括:
- 獲取活動內已存在的碎片,調用
findFragmentById()
方法(針對於那些提供了UI的碎片),或者調用findFragmentByTag()
方法(針對那些有或沒有提供UI的碎片)。 - 從"後退堆棧"裏彈出碎片,利用
popBackStack()
方法(模擬用戶的返回(Back)命令)。 - 爲"後退堆棧"上發生的變化註冊一個監聽器,使用
addOnBackStackChangedListener()方法
來註冊。
更多關於這些方法或其他方面的信息,參考FragmentManager
類。
如上一章節所述, 你可以使用FragmentManager
打開一個FragmentTransaction
(碎片事務),它允許你執行碎片事務,諸如添加和刪除碎片。
執行碎片事務(Performing Fragment Transactions)
在活動裏使用碎片的一個很大特點是,作爲對用戶交互的反應,你能夠添加,移除,替換以及通過碎片執行其他的行爲。你提交給活動的每一個變化集被稱作"一次事務",你可以使用FragmentTransaction
裏的API執行一次事務。你還能把每個事務保存到由活動管理的"後退堆棧"裏,允許用戶向後瀏覽以遍及碎片曾發生過的變化(類似於貫穿於活動的向後瀏覽一樣)。
你能夠從 FragmentManager
中取得一個FragmentTransaction
的實例,像這樣:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每個事務就是一個你想同時執行的改變集合。你可以使用諸如add()
,remove()
和replace()
的方法來爲一個給定的事務組織所有你想要執行的改變。然後,爲了把事務應用到活動,你必須調用commit()
方法。
然後,在調用commit()
前,你可能想調用addToBackStack()
方法以把此次事務添加到碎片事務後退堆棧裏。這個後退堆棧由活動來管理,並允許用戶通過按下"Back"按鈕回到先前的碎片狀態。
這裏有一個告訴你如何用一個碎片替換另一個,並把先前的狀態保存到後退堆棧裏的例子:
// 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
標示的佈局容器內的碎片。通過調用addToBackStack()
,"取代事務"被保存到了後退堆棧裏,所以用戶能夠反轉事務,並通過按下"Back"按鈕取回先前的碎片。
如果你在事務中添加了多個改變(比如其他的add()
或remove()
),並調用addToBackStack()
,
那麼在你調用commit()
前,所有被實施的改變作爲單個事務被添加到了後退堆棧裏,且"Back"按鈕會將他們一起反轉。
向 FragmentTransaction
添加改變的順序是無關緊要的,除了:
- 在最後調用
commit()
。 - 如果你正同一個容器內添加多個碎片,那麼它們被添加的順序決定了它們出現在視圖層級結構中順序。
如果,在執行了一個移除碎片的事務時沒有調用addToBackStack()
,那麼,當此次事務被提交時,那個碎片就被銷燬了,用戶也不能向後瀏覽到它。但是,當移除一個碎片時調用了addToBackStack()
,則那個碎片就被停止了,並在用戶向後瀏覽時被重新恢復。
提示:對於每次碎碎片事務,在它被提交前,通過調用setTransition()
方法你可以申請一個事務動畫。
調用commit()
時並不會立刻執行事務。相反,它被調度到活動UI線程(主線程)上,主線程會盡快運行它。然而,如有需要,你可以從你的UI線程裏調用
executePendingTransactions()
來立刻執行由commit()
提交的事務。通常沒有必要這麼做,除非此事務依賴於其他線程的工作。
警告:你只能在活動保存它的狀態(當用戶離開活動)之前使用commit()
提交事務。如果試圖在此之後提交,將會拋出一個異常。這是因爲,如果活動在需要被恢復時,在保存狀態之後的提交都被丟失了。
對於在不出現問題的情況丟棄提交,可以使用commitAllowingStateLoss()
。
與活動溝通(Communicating with the Activity)
儘管碎片可以被實現爲一個獨立於活動的對象,並能被嵌在多個活動內部,但是,碎片的一個特定實例被直接綁定到包含它的活動上。
具體來說,碎片可通過getActivity()
方法訪問活動實例,並很容易地執行諸如在活動佈局裏查找視圖的工作。
View listView = getActivity().findViewById(R.id.list);
同樣,
使用findFragmentById()
或findFragmentByTag() 方法
,活動可以通過從FragmentManager
獲取一個
Fragment
引用,調用其內部的方法。例如:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
創建活動的事件回調(Creating event callbacks to the activity)
在某些情況下,你可能需要碎片與活動分享事件。一個可以做到這一點的好辦法是,在碎片聲明一個回調接口,並在宿主活動內來實現它。當活動經由該接口收到回
調時,它可以根據需要與佈局內的其他碎片分享信息。
例如,假設一個應用的活動裏有兩個碎片,一個用來顯示文章列表(碎片A),另一個用來展示文章內容(碎片B),那麼,當一個列表項被選中時,碎片A必須告訴活動,目的是活動能夠告訴碎片B來顯示文章內容。這種情況下,在碎片A中聲明OnArticleSelectedListener
接口:
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
然後,宿主活動實現OnArticleSelectedListener接口,並重寫
onArticleSelected()
方法來通知碎片B。爲確保宿主活動實現這個接口,碎片A的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");
}
}
...
}
如果活動沒有實現此接口,那麼碎片會拋出ClassCastException
異常。一旦成功,mListener
成員就持有活動的OnArticleSelectedListener
實現的引用,目的是爲了碎片A通過調用由OnArticleSelectedListener
接口定義的方法能夠與活動分享事件。例如,如果碎片A是ListFragment
的一個擴展,每次用點擊列表項時,系統就會碎片裏調用onListItemClick()
,然後它又調用onArticleSelected()
方法來與活動分享事件:
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,活動(或其他碎片)使用它來從應用的內容提供器(ContentProvider
)中取出文章。
更多關於使用內容提供器的信息參考Content Providers文檔。
向操作欄添加項目(Adding items to the Action Bar)
碎片可以爲活動的Options Menu(和Action
Bar)貢獻菜單項,通過實現onCreateOptionsMenu()
方法。然而,爲使該方法接收到調用,你必須在onCreate()
期間
調用 setHasOptionsMenu()
,以此來預示碎片願意向選項菜單添加項目(否則,碎片將不會收到onCreateOptionsMenu()
調用)。
任何從碎片內向選項菜單添加的項目都被附加在已有菜單項的後面。當一個菜單項被選中時,碎片也可以收到onOptionsItemSelected()
回調。
注意:儘管碎片可以收到每一個由它添加的菜單項的選中回調,但是,當用戶選擇一個菜單項時,活動首先收到各自的回調。如果活動的單項選中回調實現沒有處理被選中的菜單項,那麼該事件就被傳遞給碎片的回調。對於選項菜單和內容菜單這都是真是。
更多關於菜單的信息,詳見Menus 和Action Bar 開發者指南。
處理碎片生命週期(Handling the Fragment Lifecycle)
管理碎片的生命週期很像管理活動的生命週期。和活動一樣,碎片有三個狀態:
恢復(Resumed)
碎片在運行的活動內可見。
暫停(Paused)
其他的活動位於前景並獲得焦點,但是碎片處在的活動依然可見(前景活動部分透明,或沒有完全覆蓋住屏幕)。
停止(Stopped)
碎片爲不可見。要麼是宿主活動被停止了,要麼就是碎片從活動裏被移走而被添加到後退堆棧裏。
被停止的碎片依然是存活的(所有狀態和成員信息被系統保存了)。然而,如果活動被取消了,碎片也將被取消並不再對用戶可見。
仍像活動一樣,在活動進程被取消時可以使用Bundle
保存碎片的狀態,並在活動重新啓動時恢復它們。你可以在碎片的onSaveInstanceState()
回調期間保存碎片狀態,並在onCreate()
,onCreateView()
,或onActivityCreated()
中一個裏面恢復狀態。更多關於保存狀態的信息參考Activities文檔。
生命週期在活動和碎片二者間最顯著的不同是,它們是如何被保存在各自的後退堆棧內的。默認情況下,當活動被停止時,它被放置在由系統管理的活動後退堆棧中(目的爲了用戶可以通過"Back"按鈕向後瀏覽它)。然而,只有當你在移除碎片的事務期間通過調用addToBackStack()
方法來明確地要求該碎片被保存時,碎片纔會被放置在由宿主活動管理的後退堆棧裏。
同樣,管理碎片的生命週期非常類似於管理活動的生命週期。因此,管理活動的生命週期的做法同樣適用於碎片。不過,你還需要明白的是活動的生命是如何影響碎片的生命。
配合活動的生命週期(Coordinating with the activity lifecycle)
宿主的生命週期直接影響着碎片的生命週期,以至於活動的每個生命週期回調導致了一個類似的碎片生命週期回調。例如,當活動接收onPause()回調時,活動內的每個碎片都接收
onPause()
回到。
當碎片與活動關聯是被調用(活動被傳遞到這裏)。
被調用來創建與碎片有關聯的視圖層級。
當活動的onCreate()
方法返回時調用。
當與碎片有關聯的視圖層級被移除時調用。
當碎片從活動中被取消關聯時調用。
隨着受宿主活動生命週期的影響,碎片的生命週期流程如圖(3)所示。
在此圖中,可以看到活動的每個連續狀態是如何決定碎片可以接收哪些回調方法。例如,當活動收到它的onCreate()
回調時,活動內的碎片不會收到超出onActivityCreated()
的回調方法。
一旦活動到了恢復狀態,你就可以自由地向活動裏添加和移除碎片了。因此,僅當活動處於恢復狀態時,碎片的生命週期才能獨立地改變。
然而,當活動離開恢復狀態時,碎片再次被活動推向它的生命週期。
例子(Example)
爲了把上述討論的內容集中在一起,這有一活動使用兩個碎片來創建一個雙面板佈局的實例。下面的活動包括一個碎片來顯示莎士比亞劇本書名列表,另一個碎片來顯示劇本摘要,當從列表內選擇一個書名時。它同時展示瞭如何基於屏幕配置來提供不同的碎片配置。
提示: 這個類的全部源代碼可在FragmentLayout.java
獲得。
在 onCreate()
期間,主活動按通常方式應用了一個佈局:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_layout);
}
被應用的佈局是fragment_layout.xml
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent" android:layout_height="match_parent">
<fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
android:id="@+id/titles" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent" />
<FrameLayout android:id="@+id/details" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground" />
</LinearLayout>
使用這個佈局,活動一將其加載完後,系統就會實例化TitlesFragment
(它列出了劇本書名),而FrameLayout
(顯示劇本摘要的碎片出現在這裏)佔用屏幕右側的空間,但是在起初時爲空的。正如在下面將要看到的一樣,直到用戶從列表中選擇了一項時,一個碎片才被放置進FrameLayout
。
然而,不是所有屏幕配置都足夠寬,而能夠同時並排地顯示劇本的列表和摘要。所以,上面的佈局只對橫向的屏幕配置實用,把佈局保存在res/layout-land/fragment_layout.xml裏。
因此,當屏幕處於縱向時,系統使用下面的佈局,它被保存在res/layout/fragment_layout.xml
:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
android:id="@+id/titles"
android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>
這個佈局裏只有TitlesFragment
碎片,這意味着,當設備是縱向時,僅劇本的書名可見。所以,當用戶點擊書名列表中的一項時(設備縱向),應用將啓動一個新的活動來顯示劇本摘要,而不是加載第二個碎片。
接下來,你將看到這如何在碎片類內被實現的。第一個是TitlesFragment
類,它顯示了劇本的書名列表。這個碎片類擴展了ListFragment
,並依賴它來處理列表視圖的絕大多數工作。
隨着對這段代碼的檢查,注意到當用戶點擊列表項時這裏有兩個可能的行爲:
取決於兩個佈局中哪一個是活動的,它可以在同一活動內創建一個新的碎片來顯示細節內容(添加碎片到FrameLayout
),或者是啓動一個新的活動(碎片被顯示在這裏)。
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<String>(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();
ft.replace(R.id.details, 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);
}
}
}
第二個碎片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();
}
}
}
注意,當屏幕爲橫向時,該活動結束其自己,目的爲主活動能夠接管並沿
TitlesFragment的
旁邊顯示DetailsFragment
。這是可以發生的,假如用戶開始DetailsActivity
時屏幕爲縱向的,但然後又旋轉到橫向(它重新啓動當前的活動)。
更多使用碎片的例子(及這個例子的完整代碼),參考ApiDemos(可從Samples
SDK component下載獲得)裏的實例代碼。
2012年5月4日,畢