ConstraintLayout 是在 2016 年 Google 大會上推出的一個新的布局控件,眾所周知,ConstraintLayout 有兩個優勢:使用 ConstraintLayout 編寫布局性能更高,使用 ConstraintLayout 可以通過拖拽的形式更方便地編寫布局文件。之前一直知道這個布局控件,但是一直沒有詳細地學習過,最近學習了它的使用,現在簡單記錄一下。
本文主要分為以下幾個部分:
1. 背景介紹
在面試的時候經常會被問到有沒有做過布局優化,都通過什么方式優化布局界面的?優化布局界面其中一種方式就是通過 ConstraintLayout 降低布局層級,從而避免過度測量和繪制。本篇文章重點講解 ConstraintLayout 的用法,關于 ConstraintLayout 性能方面的優勢,可以參考這篇文章:解析 ConstraintLayout 的性能優勢。
通過可視化拖拽的方式編寫 ConstraintLayout 布局界面,個人是不太推崇的,雖然它確實很方便,但是即使通過可視化拖拽的方式編寫布局之后,還是需要看懂 xml 文件中關于 ConstraintLayout 的屬性,才可以更靈活的修改布局界面,所以本文主要講解通過 xml 屬性的方式編寫 ConstraintLayout 布局界面,關于可視化拖拽的方式編寫 ConstraintLayout 布局界面,可以參考郭霖寫的這篇博客: Android 新特性介紹,ConstraintLayout 完全解析
好了,上面介紹了 ConstraintLayout 的兩個優點,下面開始進入本文的正題,通過 xml 布局屬性的方式編寫 ConstraintLayout 布局。使用之前,需要在 build.gradle
文件中添加對 ConstraintLayout 的依賴:
dependencies {
...
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
...
}
2. 詳細使用
本節主要分為 7 點介紹 ConstraintLayout 的詳細使用,那就開始吧~
2.1 相對位置
ConstraintLayout 和 RelativeLayout 是非常類似的布局控件,它們之間最大的相似之處在于都可以編寫一個控件相對于其他控件或父控件的相對位置,比如下面這個樣式的布局既可以通過
RelativeLayout 實現,也可以通過 ConstraintLayout 實現
通過 ConstraintLayout 實現的代碼如下所示:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.lijiankun24.constraintlayoutpractice.RelativePositionActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:title="@string/relative_positioning"
app:titleTextColor="@android:color/white"/>
<TextView
android:id="@+id/tv_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@color/colorPrimary"
android:padding="30dp"
android:text="A"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_b"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
<TextView
android:id="@+id/tv_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@color/colorPrimary"
android:padding="30dp"
android:text="B"
app:layout_constraintLeft_toRightOf="@+id/tv_a"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
</android.support.constraint.ConstraintLayout>
通過上面的代碼,可以看到幾個陌生的 xml 屬性,它們都是 ConstraintLayout 的 xml 屬性,比如:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
...
>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
...
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/tv_a"
...
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_b"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
<TextView
android:id="@+id/tv_b"
...
app:layout_constraintLeft_toRightOf="@+id/tv_a"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
</android.support.constraint.ConstraintLayout>
通過屬性的名字就可以猜測出它們大概的意思,比如:
-
app:layout_constraintLeft_toLeftOf
是指該控件的左邊緣和某個控件的左邊緣對齊 -
app:layout_constraintRight_toRightOf
是指該控件的右邊緣和某個控件的右邊緣對齊 -
app:layout_constraintTop_toBottomOf
是指該控件的上邊緣和某個控件的下邊緣對齊
依次類推,同樣含義的屬性還有:
layout_constraintLeft_toLeftOf
layout_constraintLeft_toRightOf
layout_constraintRight_toLeftOf
layout_constraintRight_toRightOf
layout_constraintTop_toTopOf
layout_constraintTop_toBottomOf
layout_constraintBottom_toTopOf
layout_constraintBottom_toBottomOf
layout_constraintStart_toEndOf
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf
layout_constraintBaseline_toBaselineOf
上述屬性的屬性值可以是某個控件的 id,也可以是
parent
,比如:
app:layout_constraintLeft_toRightOf="@+id/tv_a"
app:layout_constraintRight_toRightOf="parent"
對于相對位置,官方給出的示意圖如下所示:
2.2 邊距(Margins)
假如現在有如下這樣一個界面:TextViewA 上邊距離 Toolbar 30dp,在屏幕最左邊,TextViewB 上邊距離 Toolbar 30dp,左邊距離 TextView A 也是 30dp,該如何編寫布局文件呢?
該布局 xml 關鍵代碼如下所示:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
...
>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
...
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/tv_a"
...
android:layout_marginTop="30dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_b"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
<TextView
android:id="@+id/tv_b"
...
android:layout_marginLeft="30dp"
android:layout_marginTop="30dp"
app:layout_constraintLeft_toRightOf="@+id/tv_a"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
</android.support.constraint.ConstraintLayout>
在 ConstraintLayout 布局中,下面幾個 Margin 相關的屬性依然是有效的
android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom
注意:在 ConstraintLayout 中,上述屬性值只可以是正數或者0,不可以是負數。
假如現在有這樣的一個需求,當 TextViewA 顯示的時候,TextViewB 距離左邊是 20dp;當 TextViewA 可見性為 Gone
的時候,TextViewB 距離左邊是 30dp,使用 ConstraintLayout 可以很容易的實現這樣的需求。
在 ConstraintLayout 中如下這些邊距屬性就是當依據的控件變為 Gone 的時候就會生效
layout_goneMarginStart
layout_goneMarginEnd
layout_goneMarginLeft
layout_goneMarginTop
layout_goneMarginRight
layout_goneMarginBottom
2.3 居中顯示
假如說有如下圖所示的一個界面,TextViewA 在布局的正中間,如果用 RelativeLayout 和 LinearLayout 實現是很方便的,但是用 ConstraintLayout 怎么實現呢?
其實也很簡單,代碼如下:
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.lijiankun24.constraintlayoutpractice.RelativePositionActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:title="@string/centering_positioning"
app:titleTextColor="@android:color/white"/>
<TextView
android:id="@+id/tv_a"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="center"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:text="A"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
</android.support.constraint.ConstraintLayout>
在 ConstraintLayout 中并不像 LinearLayout 和 RelativeLayout 是通過 android:gravity="center"
、android:layout_gravity="center"
、android:layout_centerVertical="true"
和 android:layout_centerHorizontal="true"
設置居中位置的,而是通過 app:layout_constraintBottom_toBottomOf
、app:layout_constraintLeft_toLeftOf
、app:layout_constraintRight_toRightOf
和 app:layout_constraintTop_toBottomOf
設置該控件上下左右分別依附于指定的控件即可。
在 ConstraintLayout 中有一個非常重要的概念 ---- constraint(約束):當控件有自己的大小時,例如:wrap_content
和具體的數值時,我們為控件添加的都是 constraint (約束),這個約束有點像橡皮筋一樣會有一個拉力拉著控件,但是并不會改變控件的大小。
例如上例,在居中顯示中,TextViewA 寬度為 100dp,左邊有 app:layout_constraintLeft_toLeftOf="parent"
約束,右邊有 app:layout_constraintRight_toRightOf="parent"
約束,左邊和右邊分別有一個同樣大小的力拉著 TextViewA 控件,所以 TextViewA 會水平居中,豎直方向也是一樣的。
如果想讓上述例子中的 TextViewA 水平方向撐滿整個父控件,即如下圖所示,那該怎么做呢?
首先我們想到的是使用 android:layout_width="match_parent"
實現,這樣設置之后確實也會實現這樣的效果,但是查看 ConstraintLayout 的官方文檔發現,在 ConstraintLayout 中 match_parent
已經被 match_constraint
所取代,所以使用 android:layout_width="0dp"
更為合適(在 xml 中并沒有 match_constraint
這個屬性值,0dp 即代碼 match_constraint
值)。
Bias屬性
可以在上下和左右分別有約束的時候加上偏移率,屬性如下所示:
app:layout_constraintHorizontal_bias="0.1"
app:layout_constraintVertical_bias="0.8"
-
layout_constraintHorizontal_bias
取值范圍是[0.0 ~ 1.0],從左向右 -
layout_constraintVertical_bias
取值范圍是[0.0 ~ 1.0],從上到下
centerposition2.png
2.4 可見性對布局的影響
在 2.1 節中提到過幾個特殊的屬性:
layout_goneMarginStart
layout_goneMarginEnd
layout_goneMarginLeft
layout_goneMarginTop
layout_goneMarginRight
layout_goneMarginBottom
這些屬性是配合著 layout_marginStart
等屬性使用的,當控件的約束所依附的 target 控件的 visibility 不為 GONE 的時候,layout_marginStart
等屬性起作用,當 target 控件的 visibility 為 GONE 的時候,layout_goneMarginStart
屬性起作用
2.5 尺寸約束
ConstraintLayout 的最小尺寸
當 ConstraintLayout 的寬度或者高度為 wrap_content
時,可以通過如下屬性為 ConstraintLayout
設置最小尺寸或最大尺寸
-
android:minWidth
:最小寬度 -
android:minHeight
:最小高度 -
android:maxWidth
:最大寬度 -
android:maxHeight
:最大高度
控件的尺寸約束
在 ConstraintLayout
中,設置控件的大小總共有三種方式,分別是:
- 一個具體的屬性值,比如:
123dp
或者一個 Dimension 引用 - 使用
wrap_content
,根據自身大小決定 -
0dp
即match_constraint
控件按寬高比設置大小
在 ConstraintLayout
中,其中的控件可以按照寬高比設置其大小,前提是控件的寬或者高的尺寸至少有一個是 0dp
,然后通過 layout_constraintDimensionRatio
設置控件的寬高比,顯示出來的控件的寬和高即是設置的比例的大小,如下所示:
<!-- 此 Button 的寬和高相等-->
<Button
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1" />
在上面 xml 代碼中,Button 的寬度是一個特定值:
wrap_content
,高度是一個可變值:0dp
,寬:高 = 1 :1,則高度也就是和寬度相同的值
通過 layout_constraintDimensionRatio
設置的參數可以是:
- 一個浮點數,代表
寬/高
的比例 - 也可以是上述例子中的形式:
寬:高
當控件寬和高的值都是 0dp
時,也可以通過 layout_constraintDimensionRatio
設置寬和高的比例,比如:
<Button
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
在上面 xml 代碼中,Button 的寬是可變值:
0dp
,高也是可變值:0dp
,高:寬 = 16:9,但是 Button 有上下兩個約束:高頂到父控件的上邊緣,底頂到父控件的下邊緣,這樣 Button 的高度就固定了,再通過比例,即可得到寬 = 高 * 9/16.
2.5 鏈(Chains)
在 ConstraintLayout 中有一個非常重要的概念:鏈(Chains)
那什么才是鏈呢?在下圖所示的界面中即存在一個鏈:
上圖中 TextViewA、TextViewB 和 TextViewC 形成了一個鏈,上圖對應的 xml 代碼是:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.lijiankun24.constraintlayoutpractice.RelativePositionActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:title="@string/chains"
app:titleTextColor="@android:color/white"/>
<TextView
android:id="@+id/tv_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@color/colorPrimary"
android:padding="20dp"
android:text="A"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_b"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
<TextView
android:id="@+id/tv_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@color/colorPrimary"
android:padding="20dp"
android:text="B"
app:layout_constraintLeft_toRightOf="@+id/tv_a"
app:layout_constraintRight_toLeftOf="@+id/tv_c"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
<TextView
android:id="@+id/tv_c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@color/colorPrimary"
android:padding="20dp"
android:text="C"
app:layout_constraintLeft_toRightOf="@+id/tv_b"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
</android.support.constraint.ConstraintLayout>
從代碼中可以看到形成鏈的三個控件有以下特點:
- TextViewA 和 TextViewB 通過
app:layout_constraintRight_toLeftOf="@+id/tv_b"
和app:layout_constraintLeft_toRightOf="@+id/tv_a"
相互依賴 - TextViewB 和 TextViewC 通過
app:layout_constraintRight_toLeftOf="@+id/tv_c"
和app:layout_constraintLeft_toRightOf="@+id/tv_b"
相互依賴
這樣便形成了一個鏈,在此鏈中最左邊的控件稱為鏈頭。同樣的也可以通過上下相互依賴形成上下的鏈,最上面的控件稱為鏈頭。
在鏈頭中通過 layout_constraintHorizontal_chainStyle
或 layout_constraintVertical_chainStyle
可以設置鏈的樣式。
鏈的樣式總共有三種:
- CHAIN_SPREAD:默認樣式
- CHAIN_SPREAD_INSIDE
- CHAIN_PACKED
用一張官方的圖解釋上面幾個屬性的含義,如下圖所示:
這里需要強調下比重鏈(Weighted chain)樣式:在 CHAIN_SPREAD
樣式的鏈中,如果其中的控件的寬度或高度為 0dp,即可以通過 layout_constraintHorizontal_weight
或 layout_constraintVertical_weight
設置該控件在水平或者豎直方向的比例,類似于 LinearLayout 中的 weight 屬性的作用
2.6 Guideline
在 ConstraintLayout 中有一個特殊的輔助類:android.support.constraint.Guideline
,主要用于輔助布局,可以把它看做是一個輔助線,但是不會繪制到界面上,有水平的和垂直的。
Guideline
有以下幾個屬性:
-
android:orientation
:vertical
或horizontal
,表示此 Guideline 是水平的還是垂直的 -
app:layout_constraintGuide_begin
:表示 Guideline 的距離左邊或上邊的距離,根據方向決定是距離哪邊的 -
app:layout_constraintGuide_begin
:表示 Guideline 的距離右邊或下邊的距離,根據方向決定是距離哪邊的 -
app:layout_constraintGuide_percent
:表示 Guideline 距離左邊或上邊的百分比,根據方向決定是距離哪邊的
用一張效果圖展示 Guideline 的作用,一個 TextView 在中線的左邊 32dp 位置處,另一個 TextView 在中線右邊 32dp 處
上圖代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.lijiankun24.constraintlayoutpractice.VirtualHelpersActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:title="@string/virtual_helpers_objects"
app:titleTextColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="32dp"
android:background="@android:color/holo_blue_light"
android:text="@string/virtual_helpers_objects"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@+id/guide_line"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintVertical_bias="0.502"/>
<android.support.constraint.Guideline
android:id="@+id/guide_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="32dp"
android:background="@android:color/holo_blue_light"
android:text="@string/virtual_helpers_objects"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/guide_line"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintVertical_bias="0.502"/>
</android.support.constraint.ConstraintLayout>
好啦,至此關于 ConstraintLayout 的使用就基本全部介紹完畢,是不是覺得很好用呢?那就趕快在項目中使用起來的!文中涉及的代碼都在這兒:ConstraintLayoutPractic。
參考資料:
ConstraintLayout 完全解析 快來優化你的布局吧 -- Hongyang