Android用戶界面設(shè)計(jì):使用片段
Android 3.0引入的新的片斷(Fragment)API,讓我們更容易地創(chuàng)建動(dòng)態(tài)用戶界面。在這個(gè)教程中,我們學(xué)習(xí)如何將一個(gè)兩屏的ListView轉(zhuǎn)換成WebView流,以適應(yīng)大屏幕的單屏流設(shè)計(jì),比如在平板設(shè)備中。
這篇文章的節(jié)奏將比我們的入門教程更快一些。如果你對基本的Android控件或概念不熟悉你可能需要復(fù)習(xí)這個(gè)網(wǎng)站上我們其它的一些教程,甚至是Android API參考。最終的開源代碼可以在Google code上下載到。
片段簡介
在我們開始之間,讓我們在更高的層次上定義一下什么是片段。通常來說,片段是一大塊用戶界面,它具有自己的生存周期。如果它聽起來像一個(gè)Activity,那是因?yàn)樗_實(shí)很像一個(gè)Activity。然而,片段與Activity不同,片段必須存在于Activity之內(nèi)。片段不須要在它每次初始化的時(shí)候與同一個(gè)Activity配對,這使它具有一些靈活性。與Activity一樣,片段也無需包含任何用戶界面。
步驟0:開始
這個(gè)教程假設(shè)你讀過我們的列表視圖教程,你可以下載那個(gè)教程的代碼,并完成一些任務(wù),然后開始,也可以直接下載這個(gè)教程的代碼直接開始。
步驟1:重新設(shè)計(jì)界面
下圖示意了我上在列表視圖教程中所提到的文章閱讀應(yīng)用,我們還沒有考慮并使用片段:

這個(gè)流程在相對小屏幕上運(yùn)行得很不錯(cuò)。然而,在大屏幕上,比如Motorola Xoom平板的10寸屏幕上,在列表視圖上卻浪費(fèi)了很多空間。WebView看起來正常,但是有點(diǎn)枯燥。
這就是要引入片段的地方:在大屏幕上,我們可以提供更有效的用戶界面,如果我們可以在同一屏上顯示ListView和WebView。當(dāng)用戶點(diǎn)擊左邊“面板”的列表視圖中的某一項(xiàng)時(shí),右邊的WebView更新顯示相應(yīng)的內(nèi)容。這種工作流程經(jīng)常用于email或文檔或RSS閱讀器。下圖就是重新設(shè)計(jì)之后的界面示意圖:

步驟2:轉(zhuǎn)換為基于片段的設(shè)計(jì)
現(xiàn)在我們知道了新的流程應(yīng)該如何設(shè)計(jì),我們也知道當(dāng)前的兩個(gè)活動(dòng)必須轉(zhuǎn)換成片段。我們將分幾步來完成這個(gè)轉(zhuǎn)換。第一步保持界面樣子不變,只是使用片段修改每個(gè)界面內(nèi)容。一個(gè)片段將包含當(dāng)前的ListView,另一個(gè)包含WebView。然后我們再轉(zhuǎn)到單個(gè)屏幕的實(shí)現(xiàn),修改ListView和WebView之間的消息傳遞。
首先,將你的程序的項(xiàng)目構(gòu)建目標(biāo)改變Android 3.0。在Eclipse中,右鍵點(diǎn)擊項(xiàng)目并選擇“屬性”。點(diǎn)擊Android部分并選中Android 3.0。我們不使用任何Google API,所以Android開源項(xiàng)目版本足夠了。然后點(diǎn)擊“確定”按鈕。
現(xiàn)在你就可以訪問新的API了,包括片段API。
注意:在將來的教程中,我們將討論如何使用新的兼容層來使得像片段API這樣的技術(shù)在更早版本的Android設(shè)備上也能工作。但是現(xiàn)在它只能運(yùn)行在Android 3.0設(shè)備上。
步驟3:創(chuàng)建片段類
創(chuàng)建兩個(gè)Java類來代表兩個(gè)片段:ListView界面和WebView界面。將它們命名為TutListFragment和TutViewerFragment。TutListFragment將繼承ListFragment類,TutViewerFragment只是繼承Fragment類。
在TutListFragment類中,我們需要重寫兩個(gè)方法: onListItemClick()和onCreate()。這些方法的內(nèi)容看起來應(yīng)該很熟悉,它與之前我們講過的TutListActivity類的代碼一致。這個(gè)代碼很快就要修改,但是現(xiàn)在暫時(shí)不需要,下面是當(dāng)前TutListFragment類的代碼:
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- String[] links = getResources().getStringArray(R.array.tut_links);
- String content = links[position];
- Intent showContent = new Intent(getActivity().getApplicationContext(),
- TutViewerActivity.class);
- showContent.setData(Uri.parse(content));
- startActivity(showContent);
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setListAdapter(ArrayAdapter.createFromResource(getActivity()
- .getApplicationContext(), R.array.tut_titles,
- R.layout.list_item));
- }
TutViewerFragment類更簡單一些。我們基于當(dāng)前片段運(yùn)行在同一個(gè)活動(dòng)下并且直接從Fragment類內(nèi)問部獲取目標(biāo)數(shù)據(jù)的事實(shí)。添加一個(gè)重寫onCreateView()方法。這個(gè)方法的代碼應(yīng)該看起來像這樣:
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- Intent launchingIntent = getActivity().getIntent();
- String content = launchingIntent.getData().toString();
- WebView viewer = (WebView) inflater.inflate(R.layout.tut_view, container, false);
- viewer.loadUrl(content);
- return viewer;
- }
直接訪問活動(dòng)實(shí)例的能力非常有用,但是在后面會(huì)引起一個(gè)問題。如果這個(gè)片段存在于帶有列表片段的界面上會(huì)怎么樣呢?在那樣的情況下,就會(huì)沒有啟動(dòng)目標(biāo)來獲取URL。類似的在TutListFragment中,只要當(dāng)用戶點(diǎn)擊一個(gè)列表項(xiàng)時(shí)我們都直接啟動(dòng)一個(gè)新的Activity。如果TutViewFragment在同一個(gè)活動(dòng)中存在什么怎么樣呢?如果這樣的話,啟動(dòng)一個(gè)新的活動(dòng)就沒有意義了。我們將在這個(gè)教程的后面回過頭來解決這個(gè)問題。
步驟4:添加片段布局資源
現(xiàn)在創(chuàng)建一個(gè)新的名為“tutlist_fragment.xml”的布局文件來表示包含文章列表的片段。片段布局資源使用你創(chuàng)建的Fragment類的標(biāo)簽和引用。
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.mamlambo.tutorial.tutlist.TutListFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/tutlist_fragment">
接下來,創(chuàng)建一個(gè)類似的布局文件,叫做tutview_fragment.xml:
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.mamlambo.tutorial.tutlist.TutViewerFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/tutview_fragment">
步驟5:更新Activity類
TutListActivity和TutViewerActivity類必須修改。TutListActivity類只有一個(gè)方法,onCreate(),現(xiàn)在需要修改它來加載你在前一步創(chuàng)建的合適的片段布局資源,如下:
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tutlist_fragment);
- }
TutListActivity應(yīng)該繼承Activity類,而不是ListActivity類。
TutViewerActivity類也需要類似的修改,它的onCreate()方法現(xiàn)在看起來像這樣:
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tutview_fragment);
- }
步驟6:檢查你的進(jìn)度
嘗試現(xiàn)在運(yùn)行程序。你會(huì)發(fā)現(xiàn)它和以前一樣。沒什么值得興奮的,不是么?然而,整個(gè)用戶界面現(xiàn)在使用片段來運(yùn)行了。這使你需要做的下一步修改更加平滑,我們添加一個(gè)新的布局來組合兩個(gè)片段以在一個(gè)界面上顯示。然而可能你也注意到了,片段之間的通信的處理和我們文章之間的通信一樣。事實(shí)上,我們每個(gè)片段對應(yīng)的活動(dòng)保持不變。當(dāng)一個(gè)活動(dòng)包含并管理兩個(gè)片段時(shí),這將不符合需求。首先讓我們來修復(fù)它。
步驟7:改變TutListFragment通信
像你在步驟3中學(xué)到的一樣,從TutListFragment對象直接啟動(dòng)一個(gè)活動(dòng)不再有效了。WebView UI可能與列表是同一個(gè)活動(dòng)的一部分——總之那就是我們對于大屏幕的計(jì)劃。在那種情況下,我們只想在第二個(gè)片段中更新WebView的URL。
做這些修改,我們需要做幾件事情。首先,我們讓片段不依賴于它們所在的活動(dòng)。要做到這一點(diǎn),在TutListFragment類中添加一個(gè)偵聽器,如下:
- public interface OnTutSelectedListener {
- public void onTutSelected(Uri tutUri);
- }
然后通過更新onListItemClickListener()方法來觸發(fā)它,如下:
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- String[] links = getResources().getStringArray(R.array.tut_links);
- String content = links[position];
- tutSelectedListener.onTutSelected(Uri.parse(content));
- }
接下來讓TutListActivity類實(shí)現(xiàn)OnTutSelectedListener接口,如下:
- public class TutListActivity extends Activity implements
- TutListFragment.OnTutSelectedListener {
- ...
- @Override
- public void onTutSelected(Uri tutUri) {
- Intent showContent = new Intent(getApplicationContext(),
- TutViewerActivity.class);
- showContent.setData(tutUri);
- startActivity(showContent);
- }
現(xiàn)在我們分離了片段的功能,這些功能用于處理用戶界面,作為控制器的活動(dòng),向下一個(gè)活動(dòng)傳遞數(shù)據(jù)。我們后面要修改onTutSelected()方法來決定是否啟動(dòng)一個(gè)新的活動(dòng)實(shí)例或者更新現(xiàn)有的片段實(shí)例。
步驟8:改變TutViewerFragment通信
現(xiàn)在讓我們把注意力轉(zhuǎn)到TutViewerFragment類上,它的代碼也需要修改。片段不再查詢啟動(dòng)目標(biāo)來找出加載哪個(gè)URL,而是等待被通知要加載哪個(gè)URL。在樣,我們可以直接修改WebView而不需要每次加載都重新創(chuàng)建片段。
首先,修改TutViewerFragment類,讓它包含一個(gè)叫做updateUrl()的方法:
- public void updateUrl(String newUrl) {
- if (viewer != null) {
- viewer.loadUrl(newUrl);
- }
- }
其次,刪除所有onCreateView()方法下的功能,除了inflate()的調(diào)用。在TutViewerActivity類中,添加這些功能檢索Intent然后調(diào)用updateUrl()方法,如下:
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.tutview_fragment);
- Intent launchingIntent = getIntent();
- String content = launchingIntent.getData().toString();
- TutViewerFragment viewer = (TutViewerFragment) getFragmentManager()
- .findFragmentById(R.id.tutview_fragment);
- viewer.updateUrl(content);
- }
此時(shí)此刻,程序的行為還是沒有變化。然而通過進(jìn)一步的代碼,片段現(xiàn)在可以共存在同一個(gè)活動(dòng)中或者分開。
步驟9:添加雙片段布局
現(xiàn)在讓我們來創(chuàng)建帶有兩個(gè)片段的布局,以供特定情況使用。在layout-land目錄(你可能需要自己創(chuàng)建),粘貼一份tutlist_fragment.xml。它將對橫屏和豎屏提供不同的布局。豎屏模式將保持不變。編輯這個(gè)文件如下:
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
- android:name="com.mamlambo.tutorial.tutlist.TutListFragment"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:id="@+id/tutlist_fragment"
- android:layout_weight="45">
- android:name="com.mamlambo.tutorial.tutlist.TutViewerFragment"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:id="@+id/tutview_fragment"
- android:layout_weight="55">
這將界面分隔成水平地兩個(gè)片段(左右結(jié)構(gòu))。
步驟10:添加動(dòng)態(tài)選項(xiàng)
現(xiàn)在我們可以為程序添加一些簡單的邏輯,可以在啟動(dòng)一個(gè)新的活動(dòng)(雙屏模式)和更新存在的片段(單屏模式)之間切換。
為了達(dá)到這個(gè)目的,更新TutListActivity類的onTutSelected()方法如下:
- @Override
- public void onTutSelected(String tutUrl) {
- TutViewerFragment viewer = (TutViewerFragment) getFragmentManager()
- .findFragmentById(R.id.tutview_fragment);
- if (viewer == null || !viewer.isInLayout()) {
- Intent showContent = new Intent(getApplicationContext(),
- TutViewerActivity.class);
- showContent.setData(Uri.parse(tutUrl));
- startActivity(showContent);
- } else {
- viewer.updateUrl(tutUrl);
- }
- }
我們所做的就是獲取片段并檢查它是否是現(xiàn)存的布局的一部分。如果不是,查看器活動(dòng)啟動(dòng),否則更新已存在的片段。
步驟11:運(yùn)行最新的使用片段的程序
到此,程序?qū)⒂袃煞N模式:豎屏保持不變,橫屏顯示列表位于WebView的左側(cè)。現(xiàn)在可以做幾個(gè)改進(jìn),但是只是做微調(diào),優(yōu)化。比如,如果你在豎屏WebView模式下并旋轉(zhuǎn)屏幕,結(jié)果還是只有WebView界面。你必須點(diǎn)擊返回以獲得雙面視圖。程序修正不在這個(gè)教程講述的范圍,但是你可以發(fā)現(xiàn),如果使用適當(dāng)?shù)牟季植⑶壹由弦恍┗顒?dòng)邏輯,你可以對于不同的屏幕和設(shè)備做到非常強(qiáng)大和靈活。

總結(jié)
片段API幫助組織用戶界面組件,以使它們可以實(shí)現(xiàn)跨活動(dòng)重用。這樣,程序可以在相對少的代碼量下,動(dòng)態(tài)地適應(yīng)它的流程和用戶界面。你也能看到基于片段構(gòu)建的代碼更容易重新組織。更值得高興的是,通過Google提供的兼容庫,現(xiàn)在任何程序都可以使用片段了,它甚至兼容到Android 1.6?,F(xiàn)在就使用片段來為每一個(gè)屏幕大小和形狀創(chuàng)建你的程序用戶界面吧!
【編輯推薦】
- Android用戶界面設(shè)計(jì):基本按鈕
- Android用戶界面設(shè)計(jì):布局基礎(chǔ)
- Android用戶界面設(shè)計(jì):線性布局
- Android用戶界面設(shè)計(jì):相對布局
- Android用戶界面設(shè)計(jì):框架布局