ConstraintLayout使用指南

tinycoder發表於2018-05-01

背景

ConstraintLayout是Google在2016年I/O大會上提出的一種全新的佈局容器,其目標是簡化佈局層次,讓佈局儘可能扁平化。

優勢

它具有LinearLayout和RelativeLayotu的所有特點,如線性佈局,權重,相對位置,所以使用一層結構也可實現複雜的佈局,大大簡化了佈局的層次結構。

位置

ConstraintLayout提供了完善的約束方式,通過合理使用,我們可快速實現View的位置佈局:

app:layout_constraintTop_toBottomOf
app:layout_constraintLeft_toRightOf
app:layout_constraintRight_toLeftOf
app:layout_constraintBottom_toTopOf
app:layout_constraintStart_toEndOf
app:layout_constraintEnd_toStartOf
app:layout_constraintBaseline_toBaselineOf
複製程式碼

這些屬性都是layout_constraintX_toYOf形式,看起來很複雜,其實這只是為了清晰地表達約束條件而已,實際上在使用上我們只需要關注toYof部分,如:toBottomof表示在指定元素的底部。

既然有依賴關係,那就存在所依賴的View被隱藏的情況,與RelativeLayout不同的是,如果所依賴的View被隱藏(visibility被設定為GONE)時,它的依賴關係依然存在,同時還提供了當所依賴的View被隱藏後設定margin的功能(相當於View被隱藏了,但原來的位置提供了一個隱形的瞄點)。

app:layout_goneMarginTop
app:layout_goneMarginLeft
app:layout_goneMarginRight
app:layout_goneMarginBottom
複製程式碼

需要注意以下幾點:

  • 上面的這幾種屬性的值大多數情況都應該使用指定View的id,而不是parent, 使用parent時,當前View被放置在parent的外側,所以,只有特殊情況下才使用parent;
  • layout_goneMarginX屬性只有所依賴的一側才有效。例如,當前View在所依賴的View的底部,那麼只有layout_goneMarginTop才有效;
  • layout_constraintBaseline_toBaselineOf這個屬性,它表示以baseline對齊(baseline簡單來說,就是文字的底部,而不是View的底部),在文字對齊中很好用,使用RelativeLayout 時,對兩個文字大小不同的TextView進行底部對齊時,設定了底部對齊,還需要設定margin來手動調整,而在ConstraintLayout中,使用layout_constraintBaseline_toBaselineOf就可直接對齊。 這個屬性的優先順序比layout_constraintTop_toBottomOf和layout_constraintBottom_toTopOf要高,所以同時使用時,會覆蓋上面兩個屬性的值。

邊距

在ConstraintLayout中,我們發現layout_marginXXX不起作用,原因是沒有對元素新增約束,所以在設定邊距時,我們需要先設定約束條件,再使用layout_marginXXX屬性:

// 距離父佈局左側24dp
android:layout_marginLeft="24dp"
app:layout_constraintLeft_toLeftOf="parent"

 // 距離id為start_text的View左側24dp
android:layout_marginLeft="24dp"
app:layout_constraintLeft_toLeftOf="@id/start_text"
複製程式碼

對齊方式

在ConstraintLayout中,子元素不能使用layout_gravity屬性設定對齊方式,而只能使用下面幾個屬性:

// 頂部對齊
app:layout_constraintTop_toTopOf="parent"
// 左邊對齊
app:layout_constraintLeft_toLeftOf="parent"
// 右邊對齊
app:layout_constraintRight_toRightOf="parent"
// 底部對齊
app:layout_constraintBottom_toBottomOf="parent"
複製程式碼

這幾個元素的值可為指定元素的id或parent, 為元素id表示與指定元素對齊,設定為parent表示與父佈局的對齊方式。雖沒有提供居中對齊(水平居中,垂直居中,居中),但可通過組合的方式來實現:

// 水平居中
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"

// 垂直居中
app:layout_constraintTop_toTopOf="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"
複製程式碼

實現起來還是比較麻煩,尤其是居中,需要四個屬性組合才可以實現,但願Google能夠新增相應的屬性,簡化屬性實現過程。當然,也可自己擴充套件。

雖然ConstraintLayout沒有提供單個的居中屬性,但卻提供了一個很別緻的屬性——Bias, 表示居中情況下的偏移,其取值為[0, 1],預設為0.5。以水平方面來說,0表示左對齊,0.5表示水平對齊,1表示右對齊。當然,前提是要設定水平居中。

// 左對齊
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_bias="0"

// 右對齊
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_bias="1"

// 從水平中心點向左偏移一定的距離,偏移的距離=(ConstraintLayout.width - View.width) * 0.25
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_bias="0.25"
複製程式碼

==特別說明:==

ConstraintLayout中的View允許寬度或高度被設定為0dp, 但不能同時被設定為0dp。如果為0dp,則以wrap_content的形式顯示。 當View居中(相對於ConstraintLayout居中或相對於指定View居中)顯示時,如果是水平居中,width被設定為0dp,則width會被自動設定為所依賴的View的寬度,高度同理。

<android.support.constraint.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/text1"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:text="Hello"
        android:gravity="center"/>

    <TextView
        android:id="@+id/text2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="world"
        android:gravity="center"
        app:layout_constraintTop_toBottomOf="@id/text1"
        app:layout_constraintLeft_toLeftOf="@id/text1"
        app:layout_constraintRight_toRightOf="@id/text1"/>
</android.support.constraint.ConstraintLayout>
複製程式碼

這個佈局中,text2的寬度將被自動設定成100dp。如果設定為以父佈局居中,則寬度就是ConstraintLayout的寬度減去padding的距離。

比例

ConstraintLayout還支援比例設定,通過layout_constraintDimensionRatio即可設定View的寬高比例:

但使用時需要注意以下幾點:

  • width或height有且只有一邊需要設定為0dp, 另一邊需要設定為固定值或match_parent;

  • 至少需要一個約束條件,如位置或對齊方式,否則設定的比例無效;

  • layout_constraintDimensionRatio的值表示width:height,可設定為比例的形式,也可設定為比例值,如2:1或2。當然,也可指定為height:width:

    app:layout_constraintDimensionRatio="H,2:1" // 表示height:width=2:1

鏈條

Chains是ConstraintLayout又一獨特的地方,它可以看做是LinearLayout的另類實現。不過它只是一種邏輯上存在的概念,它的構成很簡單:相互引用自成鏈。如:ViewA的位置設定為ViewB的左側,ViewB的位置設定為ViewA的右側,那麼它們就形成了一條鏈,這條鏈最左側或頂部的一個元素被稱為鏈頭,在鏈頭中可通過layout_constraintHorizontal_chainStyle或layout_constraintVertical_chainStyle屬性設定Chain的樣式,它支援的樣式包括以下幾種:

spread:Views被等間距存放(預設樣式);
spread_inside: 第一個和最後一個View靠邊顯示,其他View等間距顯示;
packed: Views緊挨著並居中顯示;
複製程式碼

具體的顯示效果可參考下圖(圖片來自張旭童同學的blog

image

使用Chain時注意一下幾點:

  • 第一個元素需要設定為左對齊或頂部對齊,最後一個元素需要設定為右對齊或底部對齊,否則即便互相引用,也不會以Chain形式展示。
  • 鏈頭元素不能作為Chain外其他View的依賴元素,否則無法實現需要的位置關係。如ViewA需要在一條Chain的下面,則不能設定layout_constraintTop_toBottomOf為第一個元素,否則不會產生預期的效果;

前面說了,ConstraintLayout也支援權重,只需要通過layout_constraintHorizontal_weight或layout_constraintVertical_weight來實現,如:

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    app:layout_constraintEnd_toStartOf="@+id/button2"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/container_layout"
    app:layout_constraintHorizontal_chainStyle="packed"/>

<Button
    android:id="@+id/button2"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="Button"
    app:layout_constraintHorizontal_weight="1"
    app:layout_constraintEnd_toStartOf="@+id/button3"
    app:layout_constraintStart_toEndOf="@+id/button"/>

<Button
    android:id="@+id/button3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toEndOf="@+id/button2"/>
複製程式碼

這條Chain中button2將佔除了button1和button3之外所有空間,需要注意的是,設定權重時,對應的width或height必須設定為0dp, 否則設定的權重無效。

示例

上面已經對的屬性進行了介紹,有必要小試牛刀了,實現以下佈局:

image

使用LinearLayout和RelativeLayout,最少也需要2層佈局才能實現,但是使用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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="16dp"
    android:paddingRight="16dp">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button1"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintRight_toLeftOf="@id/button2"
        app:layout_constraintLeft_toLeftOf="parent"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="button2"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toRightOf="@id/button1"
        app:layout_constraintRight_toLeftOf="@id/button3"/>

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button3"
        app:layout_constraintLeft_toRightOf="@id/button2"
        app:layout_constraintRight_toRightOf="parent"/>
    <View
        android:id="@+id/view4"
        android:layout_width="120dp"
        android:layout_height="0dp"
        android:layout_marginTop="18dp"
        android:background="#e0e0e0"
        app:layout_constraintTop_toBottomOf="@id/button2"
        app:layout_constraintDimensionRatio="3:4"/>
    <TextView
        android:id="@+id/view5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="12dp"
        android:text="上海市"
        app:layout_constraintLeft_toRightOf="@id/view4"
        app:layout_constraintTop_toTopOf="@id/view4"/>
    <TextView
        android:id="@+id/view6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:layout_marginLeft="12dp"
        android:text="上海市浦東新區上海市浦東新區上海市浦東新區上海市浦東新區上海市浦東新區"
        app:layout_constraintTop_toBottomOf="@id/view5"
        app:layout_constraintLeft_toRightOf="@id/view4"/>
    <TextView
        android:id="@+id/view7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="12dp"
        android:text="上海市浦東新區"
        app:layout_constraintLeft_toRightOf="@id/view4"
        app:layout_constraintBottom_toBottomOf="@id/view4"/>
</android.support.constraint.ConstraintLayout>
複製程式碼

總結

雖然ConstraintLayout很強大,但通過真實的應用,感覺還是有點複雜(需要設定的屬性太多),所以在編寫佈局時,還是應該優先考慮RelativeLayout,對於複雜的佈局,可考慮使用ConstraintLayout。

參考連結

ConstraintLayout 屬性詳解 和Chain的使用

ConstraintLayout官方文件

相關文章