讀古今文學網 > Android程序設計:第2版 > Fragment和佈局 >

Fragment和佈局

在前面幾節中,我們描述了如何開發使用Fragment項目,並且在蜂巢版本之前的Android系統上編譯它。但是,對於大多數應用而言,這僅僅只是個開始。應用可以在Froyo設備上編譯並不代表它能夠很好地在Froyo設備上運行。實際上,要想達到在任何情況下UI效果都很理想是非常困難的。本節將會描述其中一些問題,並提出一些解決方案。

坦白而言,這不是什麼好事。應用在其生命週期中,可能會運行在1080i大屏幕電視上,全家人在後座觀看;也可能是在12寸的平板上運行,通過橫向模式查看;或者在手機上運行,通過縱向模式查看;因此UI展現不可能是唯一的。要達到良好的UI效果,唯一的途徑是在設計這些UI時,考慮到不同屏幕大小這些問題。把UI導入到另一台設備需要的定制工作可能僅僅是調整一下圖片和字體,也可能需要對UI的整個工作流程做很大修改。

使UI適用於不同的設備的最簡單的方式是使用配置限定符(configuration qualifier)。配置限定符只不過是在應用的資源路徑樹的路徑名稱後面添加特定的後綴。它支持應用包含多個不同的資源版本,每個版本都和特定內容相關。

舉個例子,一個活動的屏幕是在單個佈局文件中:main.xml。該佈局是通過常量R.layout.main和代碼關聯,在…/res/layout/main.xml文件中定義項目資源。

通過配置限定符,同一個常量R.layout.main可能有不同的定義,其中一個在…/res/layout/main.xml文件中定義,另一個在…/res/layout-port/main.xml文件中定義。Android系統在運行時會根據當前設備的顯示方式,把該常量解析成橫向或縱向模式。

配置限定符不僅包含屏幕方向、像素密度、屏幕縱橫比以及絕對尺寸這些因素,還涵蓋一些其他方面,如語言、區域、停靠模式(docking mode)以及白天/黑夜顯示模式。一個資源常量可能包含成千上萬個變量,每個變量表示一組限定符組合,雖然這種情況極少存在,但也是有可能的。

配置限定符並不會使得處理各種不同的場景變簡單;而只是使之變成可能。Eclipse開發者工具對限定符提供了最小支持。Wizard的第二個頁面創建新的資源文件(File→New→Other→Android XML Values File;當命名新的資源文件後,按下Next按鈕)列出了配置限定符,支持添加任意多少的配置限定符。

使用Fragment功能的UI開發人員對配置限定符情有獨鍾,因為它們支持橫向和縱向模式的自然切換。對於以橫向模式查看的西方讀者,Fragment UI自然就會把上下文信息放到屏幕的左側邊欄,把保存詳細信息的fragment放到右側邊欄,如圖7-3所示。

圖7-3:經典的橫向Fragment佈局

在縱向模式下,顯然以上佈局是不合理的。然而,通過配置限定符創建佈局的橫向和縱向變量是很簡單的,只需要改變LinearLayout變量,把橫向fragment改成縱向fragment。圖7-4顯示了經過小小的修改後,在縱向模式下,UI效果看起來也很好。

圖7-4:縱向Fragment佈局

以上展現方式的假設是屏幕有很多空間,可以把單個屏幕劃分成多個區域。需要記住的是,fragment對於大屏幕是很有效的展現方式,但是在UI設計中使用它來改進顯示效果往往並不可行。雖然當今有些手機的屏幕尺寸很大,可以支持fragment,但是大部分設備還不支持。在這些設備中,你需要把新的、基於fragment的UI平滑地切換為Android原始的、撲克牌大小的用戶界面。

可以通過幾種方式來選擇UI風格。一種方式是維護多種不同的應用版本,在Android市場中通過manifest文件的market filters來區分。舉個例子,為小尺寸屏幕定制的manifest文件可能包含如下內容:


<supports-screens
                android:largeScreens=\"false\"
                android:xlargeScreens=\"false\" />
  

為大尺寸屏幕定制的manifest文件可能包含如下內容:


<supports-screens
                android:smallScreens=\"false\"
                android:normalScreens=\"false\"
                android:largeScreens=\"true\"
                android:xlargeScreens=\"true\" />
  

不同應用版本如果除了屏幕尺寸大小不同以外,還有其他因素,以上這種定制的展示方式就很有意義了。舉個例子,某個應用版本希望在特定的環境中使用(可能是對汽車中某個版本的目標進行擴展,監測汽油消耗),該場景包括特定尺寸的屏幕,使用Market過濾器來選擇UI風格可能是很有效的。

在個別情況下,通過market filter使得UI風格匹配不同的設備可以正常工作,但是這種方式通常會帶來很多麻煩。我們簡單看一下market filter屬性的文檔,就會發現區分不同屏幕尺寸這個功能正處於變更過渡期。對於版本13,在前面實例中使用的...Screens屬性已經被android:requiresSmallestWidthDp屬性替代了。遺憾的是,Market當前過濾功能尚未包含該屬性。

在運行時選擇基於fragment的UI還是card-stack-based的UI的標準是:以更明智的方式使用配置限定符。例如,以圖7-3所描述的基於fragment的UI佈局為例,該應用是個簡單的聯繫人視圖:點擊左側邊欄的聯繫人姓名就會在右側欄的fragment中顯示其詳細信息。其佈局看起來如下:


<LinearLayout
            xmlns:android=\"http://schemas.android.com/apk/res/android\"
            android:orientation=\"horizontal\"
            android:layout_
            android:layout_
            >
            <ListView
                android:id=\"@+id/contacts\"
                android:layout_
                android:layout_
                android:layout_weight=\"1\"
            />
            <FrameLayout
                android:id=\"@+id/contact_detail\"
                android:layout_
                android:layout_
                android:layout_weight=\"2\"
                android:background=\"@color/blue\"
            />
</LinearLayout>
  

我們前面已經介紹了如何使用配置限定符在單個應用中創建不同的佈局版本。在前面的例子中,我們又增加了一種類似的佈局,它對縱向模式顯示進行了優化,並使用配置限定符告訴Android運行時使用哪一種佈局。

切換回statck-of-cardsUI風格的技巧在於創建新的佈局,該佈局不包含任何Fragment組件,代碼如下:


<LinearLayout
            xmlns:android=\"http://schemas.android.com/apk/res/android\"
            android:orientation=\"horizontal\"
            android:layout_
            android:layout_
            >
            <ListView
                android:id=\"@+id/contacts\"
                android:layout_
                android:layout_
                android:layout_weight=\"1\"
            />
</LinearLayout>
  

剩下要做的就是修改代碼,使得它在創建fragment之前可以進行檢查,確保有足夠的地方可以放置該fragment。如果沒有地方放置該fragment,就切換回基於stack的UI風格。

stackFragment方法和stackActivity方法分別實現了fragment UI風格和statck-of-cardsUI風格。如例7-1所示,stackFragment方法使用事務管理器來替換當前的fragment;而stackActivity方法是通過Intent,以通用方式啟動新的活動。把聯繫方式的詳細信息抽像成一個通用類,UI類ContactDetailsActivity和ContactDetailsFrament都使用該類,這樣看起來相對更簡潔明瞭。

在運行時,還有一種方式可以對基於Fragment的UI和card-stack-based的UI進行選擇:使用startActivityFromFrament類的Activity方法。該方法會攔截fragment啟動intent。

要在示例程序中使用該技術,我們需要採取有些不同的策略。不論是哪一種UI風格,左側邊欄面板的列表視圖是個fragment。該fragment總是啟動intent,顯示詳細信息。如果應用是在足夠小的屏幕上,需要stack-of-cards的UI風格,intent會啟動詳細信息活動。相反地,如果應用可以使用fragment UI風格,根活動會攔截所有啟動操作,而是顯示ContactDetailsFragment。這段代碼看起來和之前的很相似,都包含佈局、card-stack和fragment。第一種定義只是在目錄res/layout-small(內容限定符「small」),而第二種定義是在佈局路徑中,包含其他限定符(比如「large」)。

以下是根活動代碼段。R.layout.main類會自動調用ContactsFragment類,StartActivityFromFragment方法基於佈局項決定是否啟動DetailFragment實例。如果沒有佈局項,會啟動新的activity實例。

最後要說明的是,這段代碼是ContactFragment類的實現。如果點擊列表視圖,該類一般會作為intent實例轉發。根活動可以選擇轉發該intent實例,或者把請求作為新的fragment實例。