ConstrainLayout 是什麼?

ConstraintLayout可以用來製作大型複雜的布局且沒有巢狀視區群組,類似於RelativeLayout但又比RelativeLayout更有彈性。
ConstraintLayout目前(20190816)已被移至androidx 套件中,套件位置可參考。https://dl.google.com/dl/android/maven2/index.html(穩定版本為1.1.3,beta版本為2.0.0-beta2)
ConstraintLayout可用於API level 9以上可以適用於大多數的裝置。ConstraintLayout操作方式可以透過佈局編輯器的可視化工具來編輯也可以直接修改佈局檔。
 

ConstraintLayout 概述

在ConstraintLayout中的視圖元件(view)最少需要定義一個水平方向和一個垂直方向的約束,每個約束的目標對象可以是另一個視圖,父元件或引導線等等。
如下就是TextView具有水平方向以及垂直方向的約束,水平方向約束為左邊貼齊父元件左邊,垂直方向約束為上邊貼齊父元件上邊。

<TextView
  android:id="@+id/constraint_layout_tv_left_top_parent"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="lt to lt and tp to tp"
  app:layout_constraintLeft_toLeftOf="parent"
  app:layout_constraintTop_toTopOf="parent"/>

官方介紹參考
https://developer.android.com/reference/android/support/constraint/ConstraintLayout#Chains
https://developer.android.com/training/constraint-layout
官方範例
https://github.com/googlesamples/android-ConstraintLayoutExamples
 

ConstraintLayout 相依性

在project的build.gradle加入google repo

buildscript {
    repositories {
        google()
    }
}
allprojects {
    repositories {
        google()
    }
}

在Module的build.gradle加入

dependencies {
     implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
 }

 

CostraintLayout 的約束類型

Relative Position(相對定位)
Margin(邊距)
Circular positioning(圓狀定位)
Chains(鏈)
Virtual Helper objects(虛擬幫助物件)
另外注意不能在約束中有循環依賴關係。
可以參閱ConstraintLayout.LayoutParams以獲取佈局屬性
 

Relative Position 相對定位

相對定位是根據目標元件設定位置的定位方法,目標元件可以是某個元件或父元件或是虛擬幫助物件(引導線或分界線)。
Relative Position和Relative Layout的用法相當類似,RelativeLayout也是透過android:layout_above等等來指定元件相對於另一個元件的位置。
相對定位的種類可以分為水平和垂直,水平定位有right, left, start, end,垂直定位有top, bottom, baseline。
圖示左邊為垂直定位,右邊為水平定位

可以使用的屬性如下

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_constraintBaseline_toBaselineOf
layout_constraintStart_toEndOf
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf

可以把屬性解釋為
layout_constraint目前元件邊_to目標元件邊Of=”目標元件”
範例如下

<TextView
  android:id="@+id/constraint_layout_tv_left_top_parent"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="lt to lt and tp to tp"
  app:layout_constraintLeft_toLeftOf="parent"
  app:layout_constraintTop_toTopOf="parent"/>

第6行代表目前元件的左邊貼齊目標元件(父元件)的左邊。
第7行代表目前元件的上邊貼齊目標元件(父元件)的上邊。
圖示如下

目標元件也可以改為某個元件,使用時只要指定目標元件的id即可。
現在新增另一個元件(top to bottom and left to left),該元件的上邊會貼齊目標元件(lt to lt and tp to tp)的下邊,左邊貼齊左邊如下

  <TextView
    android:id="@+id/constraint_layout_tv_left_top_parent"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="lt to lt and tp to tp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>
  <TextView
    android:id="@+id/constraint_layout_tv_top_bottom"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="top to bottom and left to left"
    app:layout_constraintTop_toBottomOf="@+id/constraint_layout_tv_left_top_parent"
    app:layout_constraintLeft_toLeftOf="@+id/constraint_layout_tv_left_top_parent"/>

圖示如下

需要注意的是水平置中為

app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"

垂直置中為

app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"

水平及垂直置中

app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"

 

Margins 邊距

邊距是用來增加指定邊的距離,需要注意的是想增加的邊距必須是已存在相對的約束才有效果。如下
假設目標元件(center parent)已被設定為垂直和水平置中於父元件

<androidx.constraintlayout.widget.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=".margins.MarginsActivity">
  <TextView
    android:id="@+id/margins_activity_tv_center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="center parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

現在有一元件(margin top 50)下邊貼齊center parent的下邊,左邊貼齊左邊。如下

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="margin top 50"
  android:textColor="@color/colorAccent"
  app:layout_constraintBottom_toBottomOf="@id/margins_activity_tv_center"
  app:layout_constraintLeft_toLeftOf="@id/margins_activity_tv_center"
  />

margin top 50元件想增加上邊距50dp,把增加上邊距50dp加入如下

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="margin top 50"
  android:textColor="@color/colorAccent"
  app:layout_constraintBottom_toBottomOf="@id/margins_activity_tv_center"
  app:layout_constraintLeft_toLeftOf="@id/margins_activity_tv_center"
  android:layout_marginTop="50dp"
  />

這種做法沒有效果,因為目前元件(margin top 50)並沒有對目標元件有Top的約束,只有Bottom和Left的約束。
因此想讓android:layout_marginTop=“50dp”有效果,必須在目標元件內加入constraintTop如下

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="margin top 50"
  android:textColor="@color/colorAccent"
  app:layout_constraintTop_toTopOf="@id/margins_activity_tv_center"
  app:layout_constraintLeft_toLeftOf="@id/margins_activity_tv_center"
  android:layout_marginTop="50dp"
  />


至於是constraintTop_toTopOf還是constraintTop_toBottomOf都可以,只要目標元件具有constraintTop的約束即可。
以下是可以使用的margin

android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom

注意margin只能接受大於等於0的數值。
另外當目標元件的可視性為gone時,可以透過layout_goneMarginXXX設定對應的邊距。接續上面的範例,若希望margin top 50在center parent的可見性為gone時,增加上邊距改為100,如下

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="margin top 50"
  android:textColor="@color/colorAccent"
  app:layout_constraintTop_toBottomOf="@id/margins_activity_tv_center"
  app:layout_constraintLeft_toLeftOf="@id/margins_activity_tv_center"
  android:layout_marginTop="50dp"
  app:layout_goneMarginTop="100dp"
  />

 

Centering positioning and bias 置中定位和偏移

置中定位即為水平置中以及垂直置中。
水平置中為

app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"

垂直置中為

app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"

水平及垂直置中

app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"

 

bias 偏移

當單純使用水平置中或是垂直置中會讓目標元件置中也就是讓目標元件位於中心位置,但若想讓目標元件不這麼置中,而想偏移一些位置時就可以使用bias。
水平置中偏移為
layout_constraintHorizontal_bias
當水平置中偏移為0.5時就是預設的水平偏移位置。
可以把水平置中偏移當作從左邊移到右邊的位置,數值越靠近0就越靠近左邊,越靠近1就越靠近右邊
垂直置中偏移為
layout_constraintVertical_bias
當垂直置中偏移為0.5時就是預設的垂直偏移位置
可以把垂直置中偏移當作從上邊移到下邊的位置,數值越靠近0就越靠近上邊,越靠近1就越靠近下邊
範例如下
假設目前元件已經被設定為垂直置中了

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="center parent"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintTop_toTopOf="parent"
  />

現在想讓目標元件靠近上邊則可如下設定

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="center to ver"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintTop_toTopOf="parent"
  app:layout_constraintVertical_bias="0.1"
  />


 
水平偏移也是相同的做法。但需要注意以下的限制。
偏移必須有相對的約束才有效。
垂直偏移必須在constraintBottom 以及 constraintTop都存在的情況才有效。
水平偏移必須在constraintLeft以及 constraintRight都存在的情況才有效。

Circular positioning (1.1) 圓狀定位

可以透過角度和半徑來約束目標元件和目前元件的位置。使用方法如下
layout_constraintCircle : references another widget id
layout_constraintCircleRadius : the distance to the other widget center
layout_constraintCircleAngle : which angle the widget should be at (in degrees, from 0 to 360)
基本上就是決定目標元件,半徑,角度。
範例如下

<TextView
  android:id="@+id/center_parent"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="center parent"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintLeft_toLeftOf="parent"
  app:layout_constraintRight_toRightOf="parent"
  app:layout_constraintTop_toTopOf="parent"/>
<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="current"
  app:layout_constraintCircle="@id/center_parent"
  app:layout_constraintCircleAngle="360"
  app:layout_constraintCircleRadius="100dp"/>

current元件會把center parent當作中心點,移動到角度為360度,半徑為100dp的位置

 

Dimensions constraints 尺寸約束

可以為ConstraintLayout定義最大和最小的寬度和高度。如下
android:minWidth   -> set the minimum width for the layout
android:minHeight  -> set the minimum height for the layout
android:maxWidth  -> set the maximum width for the layout
android:maxHeight  -> set the maximum height for the layout
 
Widgets dimension constraints 元件尺寸約束
元件的尺寸(width and height)可以透過3種方式來設定
a. 使用1個指定的尺寸
b. 使用wrap_content以符合內容大小
c. 使用0dp等同於MATCH_CONSTRAINT,會受margin影響
c. 用法比較特別需要另外注意。
當width = 0dp,constraintLeft_toLeftOf = “parent” 且 constraintRight_toRightOf =”parent”,代表元件寬度會填滿父元件的寬度。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".dimensionsconstraint.DimensionConstraintActivity">
  <Button
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="top"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    />
</androidx.constraintlayout.widget.ConstraintLayout>


0dp會受margin影響,增加左右兩邊margin 50dp如下

<Button
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:text="top"
  android:layout_marginLeft="50dp"
  android:layout_marginRight="50dp"
  app:layout_constraintLeft_toLeftOf="parent"
  app:layout_constraintRight_toRightOf="parent"
  />


 
若約束參考到另一個元件,其width & height也會受影響,如下

<Button
  android:id="@+id/dimension_constrraint_btn_left_top"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="left top"/>
<Button
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:text="top center"
  app:layout_constraintLeft_toRightOf="@+id/dimension_constrraint_btn_left_top"
  app:layout_constraintRight_toLeftOf="@+id/dimension_constrraint_btn_right_top"/>
<Button
  android:id="@+id/dimension_constrraint_btn_right_top"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="right top"
  app:layout_constraintTop_toTopOf="parent"
  app:layout_constraintRight_toRightOf="parent"/>

top center雖然width是0dp,但其左右約束都參考到其他元件,因此不會填滿父元件。

建議在ConstraintLayout的元件不要使用MATCH_PARENT,相同的效果可以透過MATCH_CONSTRAINT加上left, right, top, bottom來達到
 
WRAP_CONTENT:強制約束(1.1
如果尺寸設置為WRAP_CONTENT,則在1.1之前的版本中約束不會限制尺寸。
在某些情況下希望使用WRAP_CONTENT且強制執行約束以限制結果。可以透過以下的屬性達成:
app:layout_constrainedWidth=”true|false”
app:layout_constrainedHeight=”true|false”
 
MATCH_CONSTRAINT尺寸(1.1
當尺寸設置為MATCH_CONSTRAINT時,預設會占用所有空間。 還有幾個額外的屬性可以使用:
layout_constraintWidth_min和layout_constraintHeight_min:將設置尺寸的最小大小
layout_constraintWidth_max和layout_constraintHeight_max:將設置尺寸的最大大小layout_constraintWidth_percent和layout_constraintHeight_percent:將設置尺寸的大小設置為父元件的百分比
Min and Max
Min和max的單位可以設定為xxdp,或是wrap代表wrap_content。

Percent dimension 百分比尺寸

Percent dimension類似於LinearLayout中的設定weight的設定,使用百分比尺寸需要設定以下的內容。
1.尺寸(width or height)必須設定為0dp(match_constraint)
2.app:layout_constraintWidth_default=”percent” 和app:layout_constraintHeight_default=”percent”的預設值應該設定為percent
3.layout_constraintWidth_percent or layout_constraintHeight_percent 為0到1之間的數值
範例如下,設定3個TextView寬度各佔其父元件的0.33333

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <TextView
    android:id="@+id/top_button"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:text="top"
    android:textSize="100sp"
    android:background="@color/colorPrimary"
    app:layout_constraintHeight_percent="0.33333"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>
  <TextView
    android:id="@+id/down_button"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:text="middle"
    android:textSize="100sp"
    android:background="@color/colorAccent"
    app:layout_constraintHeight_percent="0.33333"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/top_button"
    />
  <TextView
    android:id="@+id/middle_button"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:text="bottom"
    android:textSize="100sp"
    android:background="@color/colorPrimaryDark"
    app:layout_constraintHeight_percent="0.33333"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/down_button"
    />
</androidx.constraintlayout.widget.ConstraintLayout>

圖示如下

 

Ratio(寬高比)

可以設定元件的寬高比,首先設定其中一個方向(寬或高)為0dp,再透過layout_constraintDimensionRatio 設定比例,如下

  <Button
    android:layout_width="0dp"
    android:layout_height="100dp"
    android:text="bottom button"
    app:layout_constraintDimensionRatio="2:1"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    />

因為ratio為2:1,而width為0dp,代表width會隨著height變化,而height為100dp,因此width為200dp。
若將width或height都設定為0dp,系統會嘗試滿足最大的比例如下

  <Button
    android:id="@+id/button"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:text="top button"
    app:layout_constraintDimensionRatio="2:1"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

設定ratio為2:1,width和height都為0dp。此時系統會讓width為填滿父元件,而height為width的一半。
圖示如下

chains 

鏈提供了對單一方向的元件群組。另一個方向可以獨立變化。
若是一個元件群組被雙向(反方向)連結在一起,就可視為1個鏈,如下圖。

 
Chain heads 鏈首
鏈首為鏈的第一個元素,水平方向的鏈首為最左邊的元件,垂直方向的鏈首為最上方的元件。

Margins in chains 鏈中的邊距

如果鏈中指定了邊距,會套用在鏈中的每個元件之間。使用邊距時,其效果是相加的。
若一個水平鏈有一個元素設定右邊距為10dp,而下一個元素的左邊距為5dp則這兩個元素的邊距為15dp
 
Chains Style 鏈的類型

可以在鏈首元素設定layout_constraintHorizontal_chainStyle or layout_constraintVertical_chainStyle以指定鏈的類型,預設是CHAIN_SPREAD。
以下是鏈的類型
CHAIN_SPREAD
鏈中的元素將會展開(預設),範例如下,2個按鈕的水平鏈

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".Chains.ChainsActivity">
  <Button
    android:id="@+id/activity_chains_first"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="first"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/activity_chains_second"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintHorizontal_chainStyle="spread"
    />
  <Button
    android:id="@+id/activity_chains_second"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="second"
    app:layout_constraintLeft_toRightOf="@+id/activity_chains_first"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="@id/activity_chains_first"
    />
</androidx.constraintlayout.widget.ConstraintLayout>


 
Weighted chain
在CHAIN_SPREAD中,如果有元素設定為MATCH_CONSTRAINT(0dp)將會使用剩下的空間。
範例如下,2個按鈕的水平鏈,first按鈕的width為0dp,因此會占用剩下來的空間。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".Chains.ChainsActivity">
  <Button
    android:id="@+id/activity_chains_first"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="first"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/activity_chains_second"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintHorizontal_chainStyle="spread"
    />
  <Button
    android:id="@+id/activity_chains_second"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="second"
    app:layout_constraintLeft_toRightOf="@+id/activity_chains_first"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="@id/activity_chains_first"
    />
</androidx.constraintlayout.widget.ConstraintLayout>


CHAIN_SPREAD_INSIDE
在鏈的2端不會有空隙。
範例如下,2個按鈕,設定為chain style設定為spread_inside

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".Chains.ChainsActivity">
  <Button
    android:id="@+id/activity_chains_first"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="first"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/activity_chains_second"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintHorizontal_chainStyle="spread_inside"
    />
  <Button
    android:id="@+id/activity_chains_second"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="second"
    app:layout_constraintLeft_toRightOf="@+id/activity_chains_first"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="@id/activity_chains_first"
    />
</androidx.constraintlayout.widget.ConstraintLayout>


CHAIN_PACKED
鏈內的元件將會被連結在一起,不會有空隙。
範例如下,2個按鈕,chain style設定為 packed

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".Chains.ChainsActivity">
  <Button
    android:id="@+id/activity_chains_first"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="first"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/activity_chains_second"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintHorizontal_chainStyle="packed"
    />
  <Button
    android:id="@+id/activity_chains_second"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="second"
    app:layout_constraintLeft_toRightOf="@+id/activity_chains_first"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="@id/activity_chains_first"
    />
</androidx.constraintlayout.widget.ConstraintLayout>


CHAIN_PACKED_WITH_BIAS
鏈內的元件會被連結在一起且會透過bias屬性設定比例。
範例如下,2個按鈕,chain style設定為package,且鏈首設定 Horizontal_bias 為 0.2

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".Chains.ChainsActivity">
  <Button
    android:id="@+id/activity_chains_first"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="first"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/activity_chains_second"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintHorizontal_bias="0.2"
    app:layout_constraintHorizontal_chainStyle="packed"
    />
  <Button
    android:id="@+id/activity_chains_second"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="second"
    app:layout_constraintLeft_toRightOf="@+id/activity_chains_first"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="@id/activity_chains_first"
    />
</androidx.constraintlayout.widget.ConstraintLayout>


 

 
Weighted chains 權重鏈
鏈中的元素若使用MATCH_CONSTRAINT(0dp),預設會占用所有剩餘的可用空間。
可以加上layout_constraintHorizontal_weight 或 layout_constraintVertical_weight屬性來指定占用空間的比例。
範例如下,2個按鈕,個別設定layout_constraintHorizontal_weight為0.7,0.3

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".Chains.ChainsActivity">
  <Button
    android:id="@+id/activity_chains_first"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="first"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/activity_chains_second"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintHorizontal_chainStyle="spread"
    app:layout_constraintHorizontal_weight="0.7"
    />
  <Button
    android:id="@+id/activity_chains_second"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="second"
    app:layout_constraintLeft_toRightOf="@+id/activity_chains_first"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="@id/activity_chains_first"
    app:layout_constraintHorizontal_weight="0.3"
    />
</androidx.constraintlayout.widget.ConstraintLayout>


 
另外一個重要使用方式為占用的總空間可以指定為其他元件,不一定只能是父元件,可以透過改變鏈首起始邊的參考元件以及鏈尾結束邊的參考元件。
如下新增2個Guideline,位置各在水平方向的0.2(guideline_0.2)以及0.8(guideline_0.8)
修改first按鈕的起始邊參考到guideline_0.2,以及second按鈕的結束邊參考到guideline_0.8

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".Chains.ChainsActivity">
  <androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline_0.2"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="0.2"/>
  <Button
    android:id="@+id/activity_chains_first"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="first"
    app:layout_constraintLeft_toLeftOf="@+id/guideline_0.2"
    app:layout_constraintRight_toLeftOf="@+id/activity_chains_second"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintHorizontal_chainStyle="spread"
    app:layout_constraintHorizontal_weight="0.3"
    />
  <Button
    android:id="@+id/activity_chains_second"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="second"
    app:layout_constraintLeft_toRightOf="@+id/activity_chains_first"
    app:layout_constraintRight_toRightOf="@+id/guideline_0.8"
    app:layout_constraintTop_toTopOf="@id/activity_chains_first"
    app:layout_constraintHorizontal_weight="0.7"
    />
  <androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline_0.8"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="0.8"/>
</androidx.constraintlayout.widget.ConstraintLayout>


 

Virtual Helper objects 虛擬幫助物件

在ConstraintLayout可以使用Guideline(引導線)和Barrier(分界線)來幫助元件定位。
Guideline(引導線)
Guideline 主要是用於幫助其他元件定位,可以先在佈局檔中建立Guideline,讓其他元件根據Guideline設定位置,Guideline不會顯示在介面上,Guideline可以是水平或垂直方向。以下是一個最簡單的Guideline

<androidx.constraintlayout.widget.Guideline
  android:id="@+id/middle_guide_line"
  android:layout_width="0dp"
  android:layout_height="0dp"
  android:orientation="horizontal"
  app:layout_constraintGuide_percent="0.5"/>

有幾個屬性需要注意, Guideline會有id以讓其他元件定位,width和height通常設為0dp,orientation必須設定為vertical或horizontal以指定水平或垂直方向。
重點為Guide_percent,該值代表Guideline位於畫面上的哪個比例位置,數值為從0到1的小數點。
若是水平方向的Guideline,0為最上方,1為最下方,垂直方向的Guideline 0為最左方,1為最右方,如上例是一個percent為0.5且水平方向的Guideline代表位於畫面中心的橫線。

新增按鈕並讓其上邊貼齊Guideline的上邊

<Button
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="top middle guide line"
  app:layout_constraintTop_toTopOf="@id/middle_guide_line"
  />


比較複雜的畫面通常需要先對畫面進行規劃,就適合先建立Guideline再讓其他的元件根據Guideline進行定位。
Barrier(分界線)
Barrier和Guideline類似不會顯示,也是用來幫助其他元件定位用,主要的差別在於Barrier通常是根據多個目標元件定位,定位之後會根據目標元件的變化動態改變自己的位置。
範例如下,在畫面左側先加入Account和Password 2個EditText,再增加Barrier,Barrier會定位在Account和Password的右邊,最後加入TextView會定位在Barrier的右邊。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".barrier.BarrierActivity">
  <EditText
    android:id="@+id/account"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="Account : "
    android:textAppearance="@style/TextAppearance.AppCompat.Large"
    app:layout_constraintTop_toTopOf="parent"
    />
  <EditText
    android:id="@+id/password"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="Password : "
    android:textAppearance="@style/TextAppearance.AppCompat.Large"
    app:layout_constraintTop_toBottomOf="@+id/account"
    app:layout_constraintLeft_toLeftOf="parent"
    />
  <androidx.constraintlayout.widget.Barrier
    android:id="@+id/left_barrier"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:orientation="horizontal"
    app:barrierDirection="right"
    app:constraint_referenced_ids="account, password"
    />
  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="position to barrier"
    android:textAppearance="@style/TextAppearance.AppCompat.Large"
    app:layout_constraintTop_toTopOf="@+id/account"
    app:layout_constraintLeft_toRightOf="@+id/left_barrier"/>
</androidx.constraintlayout.widget.ConstraintLayout>

圖示如下

因為Barrier根據Account和Password定位,只要這2個元件有變化,則Barrier就會跟著變化。
在Account輸入一連串內容以改變Barrier位置,Barrier位置改變之後position to barrier也會改變。