在上一節(jié)中,我們講到了所有的 Layout 都是從 ViewGroup 繼承而來,它可以包含若干 View 并按照指定的規(guī)則將這個(gè) View 擺放到屏幕上。那么接下來的章節(jié)我們就來學(xué)習(xí)一下 Android 的 UI 布局,Android 原生有六大布局,分別是: LinearLayout(線性布局)、RelativeLayout(相對(duì)布局)、TableLayout(表格布局)、FrameLayout(幀布局)、AbsoluteLayout(絕對(duì)布局)、GridLayout(網(wǎng)格布局),我們從最簡(jiǎn)單實(shí)用的一個(gè)布局開始:LinearLayout。
1. LinearLayout 的特性
LinearLayout 繼承自 ViewGroup,可以將所包含的 View 按照線性方式一個(gè)一個(gè)的排列起來,即將 View 排列成一行(水平布局)或者排列成一列(垂直布局)。LinearLayout 有一個(gè)很關(guān)鍵的屬性:android:orientation
,可以用它來設(shè)置布局的方向,默認(rèn)是橫向。
2. 常用設(shè)置
在編寫布局代碼之前,我們先來了解一下 LinearLayout 常用的設(shè)置項(xiàng)。
2.1 基本屬性:
- id: 布局唯一 id,用來在代碼中通過 findViewById 查找該布局,獲取布局對(duì)象
- layout_height: 設(shè)置布局高度,有三種可選值:
- 具體高度(dp、px)
- wrap_content: 布局高度由子 View 的高度而定
- match_parent: 布局高度占滿父布局(等同于 fill_parent,后者已被廢棄,后文將直接使用 match_parent 替代 fill_parent)
- layout_width: 設(shè)置布局寬度,同 layout_height
- layout_gravity: 設(shè)置布局在其父布局中的對(duì)齊方式,有以下幾種常用值:
- top: 頂端對(duì)齊
- bottom: 底部對(duì)齊
- left: 居左對(duì)齊
- right: 居右對(duì)齊
- center: 居中對(duì)齊
可以組合使用,比如left|top
表示左上對(duì)齊
- gravity: 設(shè)置布局內(nèi)的各個(gè) View / Viewgroup 的對(duì)齊方式,使用方法同 layout_gravity
- background: 設(shè)置布局的背景樣式,可以用圖片或者顏色作為背景
- layout_margin: 設(shè)置元素與周圍其他元素的間距,類似的還可以設(shè)置單邊的間距:
- layout_marginRight
- layout_marginTop
- layout_marginLeft
- layout_marginBottom
以上是大多數(shù)布局都會(huì)有的屬性,在這一節(jié)講的相對(duì)詳細(xì),后續(xù)出現(xiàn)可參考本節(jié)內(nèi)容
2.2 特殊屬性
- orientation: 線性布局的方向,前面提到過可以用它決定內(nèi)部 View 的排列方向。
- layout_weight: 內(nèi)部 View 的大小權(quán)重,這個(gè)是 LinearLayout 里很重要的一個(gè)設(shè)置,它將 LinearLayout 內(nèi)部的 View 按照一定比例分配大小,具體使用后面會(huì)詳細(xì)介紹
- divider: 設(shè)置布局之間的分割線,可以通過圖片指定樣式
- dividerPadding: 分割線之間的間距
- showDividers: 設(shè)置分割線的位置,有以下可選值:
- beginning: 在元素之前展示分割線
- end: 在元素之后展示分割線
- middle: 在每個(gè)元素之間展示分割線
- none: 不展示
3. 編寫垂直樣式布局
線性布局分為垂直和水平布局兩種方式,在使用過程中除了方向不同,其余類似。本節(jié)僅演示垂直樣式,水平樣式相信你能夠觸類旁通。
顧名思義,垂直布局就是將內(nèi)部 View 從上到下依次排成一列,為了便于理解,直接上代碼,在我們新建的工程中,找到“res->layout->activity_main.xml”,編寫代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="Here"
android:background="#E71B0C"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="Is"
android:background="#E7430F"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="Imooc"
android:background="#E6D11B"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:background="#88F10D"
android:text="Android"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:background="#03A9F4"
android:text="Study"/>
</LinearLayout>
直接編譯,效果如下:
如圖,在屏幕中有 5 個(gè) TextView 按照垂直方向依次排成一列。注意,Layout 都是繼承自 ViewGroup 的,在上一節(jié)我們說過 ViewGroup 也是 View,所以我們可以推理出 Layout 里面也是可以放 Layout 的。按照這個(gè)邏輯我們還可以在垂直布局中嵌套水平布局,比如我們希望把“Here Is”和“Android Study”這兩個(gè)短語寫到一排:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#E71B0C"
android:text="Here"
android:textSize="30sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#E7430F"
android:text="Is"
android:textSize="30sp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#E6D11B"
android:text="Imooc"
android:textSize="30sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#88F10D"
android:text="Android"
android:textSize="30sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#03A9F4"
android:text="Study"
android:textSize="30sp" />
</LinearLayout>
</LinearLayout>
直接運(yùn)行:
我們將“Here”和“Is”、“Android”和“Study”這四個(gè) TextView 兩兩一組分別放到了一個(gè)水平樣式的 LinearLayout 中,這樣驗(yàn)證了,Layout 是可以嵌套使用的。
4. weight 的使用
以上是 LinearLayout 排列方式的使用,接下來講到一個(gè)很關(guān)鍵的屬性——weight,它可以使內(nèi)部 View 按照一定的比例配置尺寸,有同學(xué)可能會(huì)有疑問,前面不是有layout_height
及layout_width
用來設(shè)置尺寸嗎?那它和layout_weight
有什么關(guān)系,會(huì)不會(huì)有什么沖突?帶著這個(gè)疑問,一起學(xué)習(xí) weight 的用法吧。
layout_weight
是 LinearLayout 特有的一個(gè)屬性,它很好的利用了線性布局的特點(diǎn),讓系統(tǒng)自適應(yīng)的幫我們完成比例縮放。和你想的一樣,它和layout_width
及layout_height
密不可分,他們的相互影響,最終的尺寸有很多種計(jì)算方法。這里提供一種我認(rèn)為最簡(jiǎn)單的理解:
先按照 layout_height / layout_width 的設(shè)置分配所需大小,然后剩下的空間按照 weight 的比例分配,最終加起來的尺寸就是各個(gè) View 的最終尺寸。
關(guān)于 layout_height / layout_width 可以大致分為 3 種情況:
- 高度 / 寬度設(shè)置為 0
- 高度 / 寬度為 wrap_content
- 高度 / 寬度為 match_parent
以下就針對(duì)這三種情況詳細(xì)說明。
4.1 設(shè)置成 0 dp(重點(diǎn))
這個(gè)是最直接,最常用的設(shè)置方式,也是我們需要掌握的重中之重。如果我們將高度設(shè)置成 0 dp,那么系統(tǒng)就會(huì)直接使用 weight 的比值作為尺寸比例分配給各個(gè)子 View。我們直接在上面的代碼中進(jìn)一步修改,不考慮內(nèi)嵌的 LinearLayout,對(duì) 3 個(gè)子 View 添加 weight 屬性,并加上背景色方便區(qū)分:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#EBA2A2"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#E71B0C"
android:text="Here"
android:textSize="30sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#E7430F"
android:text="Is"
android:textSize="30sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:background="#E6D11B"
android:text="Imooc"
android:textSize="30sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:background="#AACCE7"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#88F10D"
android:text="Android"
android:textSize="30sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#03A9F4"
android:text="Study"
android:textSize="30sp" />
</LinearLayout>
</LinearLayout>
效果如下:
可以看到,3 個(gè)子 View 的高度正好是按照 1:2:3 排列。按照上面給出的計(jì)算方法,各個(gè)View的高度是 0,所以直接就是按照比例排列。
將高度/寬度寫成 0 再使用 weight 屬性是最直接最簡(jiǎn)單的方法,也是最常用最重要的方法,大家今后會(huì)經(jīng)常用到,務(wù)必掌握!
4.2 設(shè)置成 wrap_content
我們將上面的代碼中0dp
直接修改成wrap_content
再編譯,會(huì)發(fā)現(xiàn)樣式好像沒有變,比例貌似也是 1:2:3。注意,很多地方會(huì)解釋成wrap_content
也是直接按照weight
比例來分配,其實(shí)這是大錯(cuò)特錯(cuò)的。
我們?cè)诮仄辽霞由蠘?biāo)志,仔細(xì)看看尺寸:
三個(gè)View的高度大約是 169、285、400,這個(gè)比例明顯不符合 1:2:3 ,那這個(gè)比例是如何計(jì)算的呢?
我們?cè)賮砘仡櫼幌?code>weight計(jì)算方式的定義,首先我們根據(jù)wrap_content
計(jì)算高度,那么 3 個(gè)子 View 都是單行 size 相同的文字,所以本身高度一樣,剩下部分按照 1:2:3 來分配。那么經(jīng)過測(cè)量,單行高度是 54,我們將每個(gè) View 的高度減去 54,得到剩余高度:
第一個(gè)View的剩余高度:169 - 54 = 115
第二個(gè)View的剩余高度:285 - 54 = 231
第三個(gè)View的剩余高度:400 - 54 = 346
這樣一來,剩余的尺寸就剛好符合 1:2:3 了。
4.3 設(shè)置成 match_parent
match_parent
的行為是最詭異的,但是如果理解了wrap_content
,match_parent
也就不難解釋,先來看看效果,我們將代碼中的wrap_content
替換成match_parent
再來看看效果:
我們會(huì)發(fā)現(xiàn)第三塊直接消失了,這又是為什么呢?不要慌,我們還是套用一下定義。首先假定父布局高度是 X,那么 match_parent
之后每個(gè)子View的高度都是 X,這樣再按照比例分割剩下的 X - 3X。所以可以得到 3 個(gè)子 View 的高度分別是:
第一個(gè)View的高度:X + 1/6 * (X-3X) = (2/3)X
第二個(gè)View的高度:X + 2/6 * (X-3X) = (1/3)X
第三個(gè)View的高度:X + 3/6 * (X-3X) = 0
經(jīng)過計(jì)算,非常合理!
5. 小結(jié)
這是大家學(xué)習(xí)的第一個(gè) Layout,所以對(duì)屬性的講解會(huì)多也更詳細(xì),大家完全不必死記硬背,在今后熟悉了之后會(huì)發(fā)現(xiàn)其實(shí)大部分屬性都大同小異。對(duì)于 LinearLayout 還有一些其他屬性,比如前門提到過的 divider 等等,這個(gè)比較簡(jiǎn)單也比較容易理解,大家完全可以作為課后練習(xí)編寫代碼自行測(cè)試。
其實(shí)對(duì)于 LinearLayout 有很多的局限性,比如它只能按照一行或者一列排列,如果我希望從多個(gè)方向去實(shí)現(xiàn)布局, LinearLayout 就顯得很蹩腳了,接下來一章我們會(huì)介紹一種非常靈活的布局,拭目以待。