天天看點

Android中的4.0新布局控件:Space和GridLayout

Android4.0 Ice Cream Sandwich (ICS) 提供了兩種新的控件,也就是Space和GridLayout,是專門為大螢幕裝置提供更豐富的使用者互動體驗而設計。

在這之前,Android中最常用的布局類是LinearLayout,它能将它的子元素們水準排列或垂直排列。當界面布局比較複雜的時候,也可以利用它嵌套一系列分割出來的LinearLayout子布局來實作,嵌套的層數通常不宜太深,隻适合于許多簡單布局的情形。

嵌套布局有很多顯著的缺點(參考 Android Layout Tricks #1, Flattening The Stack 這些文章),總結起來有這三個方面:

  • 1.無法同時在水準和豎直方向對齊;
  • 2.嵌套太深影響性能;
  • 3.不适用于那些支援自由編輯的設計工具;

以下就是關于上述第一個問題的簡單的例子:

Android中的4.0新布局控件:Space和GridLayout

當文字字型和“Email address”标簽文字本身改變的時候,我們會希望标簽與它右邊的元件的底部基線對齊,同時讓它的右邊緣與它下方的标簽的右邊緣對齊。但如果用嵌套的線性布局做這個會很困難,因為标簽本身會去和其他元件在水準和豎直方向上自動對齊。

諸如此類的問題并不是第一次出現在Android或者更大範圍的UI工具上,但我們需要用它們豐富的平台支援,來推動我們的其他工作。

GridLayout

為了提供更好的布局支援,我們已經在4.0架構中增加了一種新的布局類即GridLayout,它通過将容器自身的真實區域切割成行列單元來解決上述問題。

Android中的4.0新布局控件:Space和GridLayout

正如上圖所示在使用GridLayout之後,“Email address”标簽就可以同時屬于那底部基線對齊的一行和那右邊緣對齊的一列。

GridLayout用一組無限細的直線将它的繪圖區域分割成行、列、單元。它支援行、列拼接合并,這就使得一個子元素控件能夠排布在一系列連續單元格組成的矩形區域。在下文中,我們将直接使用“行”、“列”、“單元格”這些術語來分别代表“行集合”、“列集合”、“單元格集合”,這裡集合的意思是指那些一個或多個連續元素。

與LinearLayout的相似性

GridLayout的所有XML API與LinearLayout有着一緻的文法規則,是以如果你已經使用過LinearLayout的話,上手GridLayout也是應該很容易的。事實上,它們之間是非常相似的,相似到直接将XML檔案中的标簽名從LinearLayout改到GridLayout而無需做其他改變,就可以實作與LinearLayout中相似的UI布局。即使不是這樣,你也仍然能借此開啟使用這種二維布局之路。

開始使用GridLayout

在Android 4.0 SDK的程式示例中,有兩個示例分别示範了如何在Java代碼和XML中使用GridLayout:

samples/ApiDemos/src/com/example/android/apis/view/GridLayout0.java

samples/ApiDemos/res/layout/grid_layout_1.xml

這裡有一個較上述XML布局中稍微簡化版的XML代碼:

<?xml version=”1.0″ encoding=”utf-8″?>

<GridLayout

xmlns:android=”http://schemas.android.com/apk/res/android”

android:layout_width=”match_parent”

android:layout_height=”match_parent”

android:useDefaultMargins=”true”

android:alignmentMode=”alignBounds”

android:columnOrderPreserved=”false”

android:columnCount=”4″

>

<TextView

android:text=”Email setup”

android:textSize=”32dip”

android:layout_columnSpan=”4″

android:layout_gravity=”center_horizontal”

/>

<TextView

android:text=”You can configure email in just a few steps:”

android:textSize=”16dip”

android:layout_columnSpan=”4″

android:layout_gravity=”left”

/>

<TextView

android:text=”Email address:”

android:layout_gravity=”right”

/>

<EditText

android:ems=”10″

/>

<TextView

android:text=”Password:”

android:layout_column=”0″

android:layout_gravity=”right”

/>

<EditText

android:ems=”8″

/>

<Space

android:layout_row=”4″

android:layout_column=”0″

android:layout_columnSpan=”3″

android:layout_gravity=”fill”

/>

<Button

android:text=”Next”

android:layout_row=”5″

android:layout_column=”3″

/>

</GridLayout>

在這裡你能注意到的第一個差別就是在以上例子中,子元素控件沒有使用WRAP_CONTENT或MATCH_PARENT這種通常出現在Android布局資源中的屬性常量。但在GridLayout中,你通常不需要使用這些屬性,這樣做的原因在GridLayout.LayoutParams的API文檔中有所解釋。

行與列的索引

第二個你會注意到的事是在上述XML資源中,子元素控件并不總是明确指定它們自身擺放在哪些單元格内。事實上GridLayout中每個子元素控件的布局參數屬性中有行與列的索引,它們一起定義了這個控件該擺放在哪個位置,但當其中一個參數或這兩個參數都沒有指定的時候,GridLayout會提供預設值而不抛出異常。

自動索引的配置設定

當子元素控件被加入一個GridLayout的時候,GridLayout會維護這一個位置指針,以及一個所謂的“高水位标志”,用來将控件擺放到那些還閑置着的單元格裡去。

Android中的4.0新布局控件:Space和GridLayout

當GridLayout的方向屬性orientation被設定成水準(horizontal),且列數屬性columnCount 也設定過時(在上圖例子中,該值為8),那麼高水位标志(上圖中紅色标記的線)将會為每一列維護一個獨立的高度值。當索引值需要建立時,GridLayout首先會通過檢查新控件的rowSpan和columnSpan屬性來計算所需單元格集的區域大小,然後從上圖中位置指針所指的位置開始,以從左到右,從上往下的順序周遊可用空間,找到的第一個可用空間的行、列索引值。

假如GridLayout的方向屬性是豎直(vertical)的,所有的算法原則都與水準時一樣,隻是将水準軸和豎直軸所扮演的角色互換一下。

如果你希望将多個視圖控件擺放在同一單元格内,你就必須要明确指定它的索引值了,因為預設的配置設定過程正如上文解釋的那樣,是将控件擺在獨立的單元格内的。

大小、外邊距以及對齊方式

在GridLayout中,定義控件大小、外邊距的做法與在LinearLayout中是一樣的。對齊、位置屬性(gravity)的使用方式也與LinearLayout中一樣,同樣使用left, top, right, bottom, center_horizontal, center_vertical, center, fill_horizontal, fill_vertical, fill這些常量值。

自适應性

與其他UI工具包着的網格布局不同的是,GridLayout不會将資料綁定到行列中。相反的,所有與對齊和布局自适應相關的東西都隻跟元件自身相關。GridLayout将那些行為模式從中剝離開來,提供了一個更加通用的系統來讓那些處于傳統深層次嵌套布局中的但隻有輕微聯系的子元素們自适應到一個單一的布局配置檔案中。

那些單元列的自适應性可以從該列中元件的gravity屬性推斷得到,如果每個元件都定義了一個gravity屬性,那麼這個列就可以認為是自适應的,否則隻能認為是不自适應的。更多關于這方面的細節可參考GridLayout的API文檔。

模拟其他布局

GridLayout并沒有加入Android平台中其他布局的所有特性,但它也擁有一系列非常豐富的特性,使得其他布局中常用的特性也能被正常地在單一GridLayout中模拟出來。

盡管LinearLayout可以被當作是GridLayout的一個特例,但當一組視圖控件的布局簡單到就是單一行或單一列對齊時,選擇用LinearLayout會更好,因為它自身就能說明容器中子元素的排版表現目的,而且還有一些性能上的優勢(當然是非常小的)。

TableLayout布局的模拟也是非常直接的,因為GridLayout支援行、列合并。同時,TableRows在GridLayout中就不需要了。對于相同的UI,使用GridLayout完成起來往往更快而且消耗更少的記憶體空間。

簡單的RelativeLayout布局通常就是将互相關聯的視圖控件組合成行與列,以此來實作網格。與标準的網格不同的是,GridLayout由系統來完成那些繁重的排版工作。通過設定GridLayout的rowOrderPreserved和columnOrderPreserved屬性,就可以把GridLayout從傳統的網格排版中解放出來,完成大部分RelativeLayout所能完成的布局——甚至是那種當子元素尺寸變化時要求網格線能互相交錯的效果。

簡單的FrameLayout排版也能夠在GridLayout的單元格内實作,因為GridLayout的一個單元格可以放置多個視圖。要在兩個視圖之間切換,隻需要将它們放在同一單元格内,在代碼中設定可見性屬性的值來切換。與LinearLayout提到的情形一樣,如果你所需要的功能就是如上描述的那樣,選擇使用FrameLayout會更好,因為那樣會有少量性能上的優勢。

GridLayout缺少的一個重要特性就是按指定比例在行、列給子元素中配置設定額外空間,這個特性在LinearLayout中是能通過weight這個屬性實作的。此項遺漏以及實作它的可能性在GridLayout的API文檔中也有所提及。

視圖控件布局操作的階段

将上文中讨論的單元格索引配置設定階段與視圖控件布局階段區分開是很有必要的。正常來講,單元格索引的配置設定階段隻發生一次,也就是在UI初始化之後。索引配置設定隻會在索引本身未指定時才發生,同時這個階段要確定所有視圖在布局階段都已經有一系列已定義好的、将它擺放在哪裡的單元格。

而視圖控件布局階段通常在索引配置設定階段之後進行,并且每當視圖的尺寸改變時都會重新計算其位置、尺寸。GridLayout會在布局過程中測量每個子元素控件的尺寸,這樣就能計算它們在網格中所需要的寬和高值。當GridLayout使用gravity屬性最終擺放每個元件在單元格中的位置後,布局階段才算完成。

盡管索引的自動配置設定隻發生一次,但從技術上來講GridLayout是一個動态布局,這意味着如果你在元件布局完成之後改變了它的方向,或者增加或者删除某些子元素,GridLayout都會重複上述過程,重新配置設定索引,讓布局重新以一合理的方式呈現。

站在性能的角度來看,了解到GridLayout的實作已經為一些通用場景比如初始化一次但經常更新布局這種場景做了優化是十分有必要的。是以,初始化步驟通常是設定一下内部資料結構,這樣布局階段會快速完成而且不消耗任何記憶體。換句話說,也就是無論是改變GridLayout的方向還是改變它子元素的數量,都比其他常用布局操作要更消耗性能。

結論

GridLayout吸納了很多Android架構中已有的通用目的的布局的功能:也就是LinearLayout、FrameLayout、TableLayout、RelativeLayout的綜合功能。比如說它提供了将高度嵌套的視圖結構替換成一個單一的高度優化過的布局的解決方案。

如果你剛開始使用Android UI,還不熟悉Android的布局類型,那麼可以考慮使用GridLayout——它提供了其他布局所擁有的大部分特性,并且擁有比TableLayout或RelativeLayout更通用的API。

我們希望FrameLayout、LinearLayout以及GridLayout的結合能提供一系列足夠豐富的特性來完成大解決布局工作而無需手寫代碼。當然花一些時間來決定優先使用哪一種布局是非常有必要的,一個最佳的選擇可以減少中間容器的使用并且提供一個更快、消耗記憶體更少的使用者界面。