Android ConstraintLayout完全解析


下載ConstraintLayout的支持包

點擊SDK Manager,如下所示:


進入Android SDK下載界面,如下所示:


勾選ConstraintLayout for Android和Solver for ConstraintLayout中對應的版本項,點擊OK,安裝完成。

引用

注意:確保你的Android Studio是2.2或以上版本。

app/build.gradle文件中添加ConstraintLayout的依賴,如下所示。

dependencies {
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
}

注:如果在Android Studio 3.0以上,可以改成如下依賴

dependencies {
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
} 

Android Studio創建的這個佈局如果不是ConstraintLayout,我們可以通過如下操作將它轉換成ConstraintLayout。



什麼是約束(Constraints)

參考文章(https://constraintlayout.github.io/basics/create_constraint.html) 作者:[Mark Allison]

ConstraintLayout 的核心基礎就是創建約束。約束定義了佈局內兩個組件之間的關係,從而控制組件的佈局位置。對於剛接觸 ConstraintLayout 但對 RelativeLayout 熟悉的開發者來說,約束佈局的工作原理很像 RelativeLayout 中通過創建組件間關係來控制佈局。

在 Android Studio 編輯器中創建約束

最容易創建約束佈局的方式是通過 Android Studio 中的 design 可視化佈局編輯器。例子都通過藍圖 Blueprint 視圖來查看展示,我們先簡單看看在 Blueprint 視圖中的 TextView 。如下所示:


清晰地可以看到 TextView 組件,以及兩個箭頭符號表示在這個 TextView 組件上存在約束將它對齊到父組件 ConstraintLayout 的左邊緣和上邊緣。待會再來看它們是如何創建的,還可以看到存在48dp上邊距與60dp左邊距讓父組件 ConstraintLayout 和 TextView 的組件邊緣之間保留了一些間隙。選擇 TextView組件就會看到如下的縮放和約束錨點。如下所示:


邊角上的小正方形是縮放的控制點,通過拖拉這些點就可以對 TextView 進行縮放。但是這個大多數情況並不是很適用,因爲使用這種方式進行縮放後的組件將保持固定的尺寸,而我們往往更需要 TextView 根據具體情況響應式大小。

每條邊中間的錨點就是約束錨點,我們就是用這個錨點來創建約束的。其中左邊和上邊的錨點裏面有藍點表示這個錨點已經存在了一個約束,相對的右邊和下邊的空心錨點則表示沒有約束。從這個例子,我們就可以看到 TextView 的佈局位置就通過定義約束來對齊了父組件。

任何繼承了 TextView 的子組件都擁有另一個錨點:被稱爲基線(baseline)。這就允許我們通過該錨點來調整組件內的文字對齊基線。選擇 TextView 後出現下方按鈕,點擊其中的 ab 按鈕來顯示這個錨點。如下所示:

在 TextView 上出現香腸狀的控制錨點就是基線約束錨點。我們可以通過給這個錨點添加約束就像下面提到給四個邊的約束錨點添加約束一樣。

另一個出現的下方按鈕中是取消約束按鈕(按鈕中存在 'x' ),點擊將移除該組件上的所有約束。如下所示:


創建錨點,我們只需要簡單的從一個組件的錨點,拖動指向到另一個組件 View 的錨點。此處的例子,我們創建另一個 TextView (id 爲 textView2 ,原來的那個 id 是 textview),而且 textView2 已有一個對齊父組件左邊的約束,我們再創建一個約束,從 textView2 的上邊到 textview 的下邊。而這個約束就會讓 textView2 對齊到 textview 正下方,如下所示:


在此處還要注意,我們創建的約束是從 textView2 的上邊到 textView 的下邊,當我們選擇這兩個組件的時候,我們只會看到 textView2 的上邊約束錨點存在約束,而 textView 的下邊約束錨點是空心的不存在約束。如下所示:


這樣的原因是約束是單向的(除非我們談論的約束是鏈接 chains ),所以這裏例子創建的約束是屬於 textView2 的,影響的也是 textView2 的佈局位置是相對於 textView 的。因爲該約束是隻屬於 textView2 的,反過來不會影響 textView 的佈局位置。

上面講到的是同級組件間創建約束,而對於一個組件要創建相對於父組件的約束,則只是簡單的將約束拖的方向到合適的父組件邊緣即可,如下所示:



在 XML 中創建約束

對於想了解在可視化佈局下真正的存儲的是如何的開發者,以下就是 上面例子的 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"
    android:id="@+id/relativeLayout2">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="36dp"
        android:layout_marginStart="60dp"
        android:layout_marginTop="48dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="15dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />
</android.support.constraint.ConstraintLayout>

代碼中的約束都是以 app:layout_constraint 開頭的屬性。我們可以看到 ConstraintLayout 中所有子組件都存在這些屬性。

注意:這些屬性設置都是使用的 app 命名空間因爲 ConstraintLayout 是像 Support libraries 也是作爲庫引入。它屬於你的命名空間 app 而不是屬於安卓框架(使用命名空間 android)。


刪除約束

上面提到我們可以通過選中組件後出現的清空按鈕來清除所有的約束。最後,我們還要介紹的是隻刪除其中一個約束。如果在 XML 源碼中可以直接去掉相應的屬性。若使用的是可視化編輯器,則通過點擊約束錨點來去除約束條件。



Chain 鏈

參考文章:(https://constraintlayout.github.io/basics/create_chains.html) 作者:[Mark Allison]

什麼是 Chain 鏈

Chain 鏈是一種特殊的約束讓多個 chain 鏈連接的 Views 能夠平分剩餘空間位置。在 Android 傳統佈局特性裏面最相似的應該是 LinearLayout 中的權重比 weight ,但 Chains 鏈能做到的遠遠不止權重比 weight 的功能。

開始創建 Chain 鏈

前面概要已經提到了 Chain 鏈是由多個 Views 組合的,所以要創建一個 Chain 鏈就需要先選擇多個想要鏈接到一起的 Views ,然後再右鍵選擇 'Center Horizontally' 或者 'Center Vertically' 來創建水平鏈或者垂直鏈。創建一個水平鏈,如下所示


首先,可以注意到 Chain 鏈兩邊末端的兩個 View 已經存在了相對於父組件的左邊緣和右邊緣的約束。 Chain 鏈的創建定義的是 Chain 鏈組件之間的間隙關係,並不影響原有的非成員間的約束。如下剛剛創建後的圖中,有很多視圖上的標識符需要解釋一下。如下所示:


觀察截圖,可以看到 Chain 鏈組件之間的連接類似於鏈條圖案,而邊緣兩端的 View 與 父組件之間的連接類似於彈窗圖案。最外面的連接圖案代表了 Chain 鏈的鏈接模式(chain mode),鏈接模式決定了 Chain 鏈如何分配組件之間的剩餘空間,你可以從 Chain 鏈每個組件下面的 “轉換 Chain 模式” 按鈕來切換 Chain 鏈模式。如下所示:


Chain 鏈模式一共有三種,分別爲:spread ,spread_inside 和 packed 。

Spread Chain 鏈模式

Chain 鏈的默認模式就是 spread 模式,它將平分間隙讓多個 Views 佈局到剩餘空間。如下所示:


Spread Inside Chain 鏈模式

Chain 鏈的另一個模式就是 spread inside 模式,它將會把兩邊最邊緣的兩個 View 到外向父組件邊緣的距離去除,然後讓剩餘的 Views 在剩餘的空間內平分間隙佈局。如下所示:


Packed Chain 鏈模式

最後一種模式是 packed ,它將所有 Views 打包到一起不分配多餘的間隙(當然不包括通過 margin 設置多個 Views 之間的間隙),然後將整個組件組在可用的剩餘位置居中,如下所示:


在 packed chain 鏈模式,打包在一起的 Views 組可以進一步通過控制修改 bias 值來控制打包組的位置,在例子中 bias 模式是 0.5 將 Views 組居中。


Spread Chain 鏈的權重

spread 和 spread inside Chain 鏈可以設置每個組件的 weight 權重,這跟 LinearLayout 的 weight 權重設置很像。當前版本(Android Studio 3.0.1)的視圖編輯器不能直接操作設置這個權重,不過我們可以通過屬性視圖(properties 視圖)來手動設置屬性。


對特定的組件設置 spread 權重,首先得選擇這個 View 組件,假設該 View 是在一個水平的 Chain 鏈中,那麼需要在屬性視圖(properties 視圖)中設置 android:layout_width="0dp" 然後修改 app:layout_constraintHorizontal_weight="1",如下所示:


這時候觀察 View 組件在 blueprint 藍圖視圖模式中的改變,它的上邊和下邊緣都從直線變成了類似手風琴的線條,這符號就表示了 spread 或 spread inside Chain 鏈模式下的被設置了權重的組件。

同時要注意的是,在 packed Chain 鏈模式下設置權重 weight 並沒有作用。就是說並不像 spread和 spread inside 模式中表現的佔據儘可能的剩餘空間,在 packed 模式下該組件就會被收縮成 0 大小。如下所示:


在 XML 中設置 Chain 鏈

雖然假如在 XML 中存在特有的屬性設置 Chain 鏈模式會比較好,但事實上並沒有特有的屬性,而是現有的約束條件的一種組合。在 XML 中設置 Chain 鏈模式只需要設置好雙向互補的約束。本文中首個例子的 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">

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="60dp"
        android:layout_marginTop="60dp"
        android:text="TextView"
        app:layout_constraintEnd_toStartOf="@+id/textView4"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="21dp"
        android:layout_marginTop="60dp"
        android:text="TextView"
        app:layout_constraintEnd_toStartOf="@+id/textView5"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/textView3"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="60dp"
        android:layout_marginTop="60dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/textView4"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

在 textView3  中設置了約束屬性 app:layout_constraintEndToStartOf="@+id/textView4" ,而相對的在 textView4 也設置了約束屬性 app:layout_constraintStart_toEndOf="@+id/textView3  " ,本質上就是創建兩個約束條件,同一對錨點但是方向相反的約束條件,這就是 Chain 鏈的定義方式。

另外,textView3 中的約束屬性 app:layout_constraintHorizontal_chainStyle="spread" 就是指定了鏈模式 spread 你可以通過修改成 spread inside 或 packed 來切換鏈模式,而且這個約束屬性必須在鏈頭,即是鏈組件中的第一個組件。

而設置鏈模式的 bias 可以通過設置約束屬性 app:layout_constraintHorizontal_bias="0.75" 從 0.0 - 1.0 。

最後,我們就可以通過設置屬性 android:layout_width="0dp" 以及 app:layout_constraintHorizontal_weight="1" 來設置 Chain 鏈中組件的權重。


參照線 guidelines

參考文章:(https://constraintlayout.github.io/basics/guidelines.html) 作者:[Mark Allison]


什麼是參照線 guidelines

如果你熟悉 UI 設計軟件你應該已經使用過參照線 guidelines 並對它的作用熟悉了。參照線 guideline 提供了視覺上的參照用於 Views 的對齊,而且不會在運行的時候顯示,只要你熟悉它的使用了就會發現它對你的對齊實現非常方便。 Google 的 Material 設計原則推薦了使用 keylines 。該文章將介紹如何通過參照線 guidelines 來快速實現這些。

創建參照線 guidelines

創建垂直參照線 guidelines 需要在 blueprint 視圖上右鍵打開上下文菜單,然後選擇 Add Vertical Guideline 即可創建。如下所示:


當前版本的視圖編輯器(Android Studio 3.0.1)默認隱藏參照線,選擇 blueprint 內的 View 即可看到參照線。

參照線 guidelines 的類型

當前的參照線 guidelines 有三種類型,默認的第一種參考線是會有一個固定的偏移向父組件的 start 邊緣(偏移量的單位是 dp)。本文開頭創建的參照線對於父組件的 start 邊緣參考線爲 16dp。爲了適配從右向左的佈局設置,所以我們應該採用 start 邊緣而不是 left 邊緣。

第二種參考線則是有一個固定的偏移向父組件的 end 邊緣。而最後一種參考線是根據父組件 ConstraintLayout 的寬度百分比來放置,而且參照線存在一個標識器,可以通過點擊這個標識按鈕來切換參考線的類型,如下所示:


向 start 和 end 類型的偏移量參照線非常適用於 keylines 的使用場景,而百分比形式的參照線則提供了類似於 PercentLayout 的一些功能。


調整參照線 guidelines

只要已經創建了參照線,我們可以通過拖動除類型標誌器以外的地方的參照線來移動。如下所示:


你可以在例子中看到,對於一些特殊位置,如左右方向的 8dp 偏移量以及居中的 50% 位置,會對參照線有吸引力。

參照線 Guideline 實現原理

對於喜歡追根尋底的開發者,我們可以更深一步看看 Guideline 的內部實現。源碼中 Guideline 類其實就是一個 View,而且它不會渲染任何東西因爲它實現了一個 final 的 onDraw() 而且固定了它的可見性爲 View.GONE ,這就決定了運行時不會顯示任何東西,而在 View 的 layout 佈局過程中它會佔據一個位置,而其他組件可以通過它來佈局對齊。所以實際上的 Guideline 只是一個極其輕量級沒有任何顯示但是可以用於約束佈局對齊的 View 組件。

在 XML 中的 Guideline

我們可以看看一個 View 約束對齊到參照線的例子:


<?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:id="@+id/relativeLayout2"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="36dp"
        android:layout_marginStart="60dp"
        android:layout_marginTop="48dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="15dp"
        android:layout_marginStart="8dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        tools:layout_editor_absoluteY="92dp" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.13" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline11"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="20dp" />

</android.support.constraint.ConstraintLayout>

參照線 Guideline 擁有了一個屬性 app:orientation="vertical" 來描述它是一個垂直的參照線。它還有屬性 app:layout_constraintGuide_begin="16dp" 來描述它是一個對齊父組件的 start 邊緣的 16dp 偏移量處。再次提醒的是,應該用 start 邊緣而不是 left 邊緣。當然切換向 end 類型的話,可以使用另一個屬性 app:layout_constraintGuide_end="..." ,切換爲百分比類型的參照線則是設置屬性 app:layout_constraintGuide_percent="0.5" 值得取值範圍爲 0.0 到 1.0 ,描述的是百分比偏移量。

而此處的 TextView 源碼則表現了,我們可以從 TextView 像對其他 View 一樣對 Guideline添加約束向量,這樣的原因就是剛剛分析的原理,因爲 Guildeline 就是一個特殊的 View 。


ConstraintLayout基礎系列之尺寸橫縱比 dimensions

參考文章:(https://constraintlayout.github.io/basics/guidelines.html) 作者:[Mark Allison]

ConstraintLayout的尺寸 dimensions

有時候,我們需要創建一些固定方向比的 View 組件,最常使用固定橫縱比的就是當 ImageView 用於展示一些固定橫縱比的圖片的時候。舉些例子,書面封面(尺寸橫縱比多種多樣),電影海報(一般是 4:6 ),電影劇照(一般是 1.85:1 或 2.39:1 ),電視劇(一般是 4:3 或 16:9 )

對於不熟悉什麼是橫縱比的,橫縱比就是表示了 View 的寬度與高度的比例 w:h 。例如,對於一個擁有橫縱比爲 4:6 擁有寬度爲 40dp 的 View 組件有着高度是 60dp ,若它的寬度改爲 30dp 則它的高度就是 45dp 。

若我們現實的圖片能保證同樣的橫縱比和像素大小,我們可以簡單的在兩個方向上使用 wrap_content 即可。然而,現實情況由於數學四捨五入等多種原因都有可能造成實際現實的一些小誤差。如果只是現實一個圖片可能不會有多大問題,但是如果多個圖片展示的時候小問題也會被有很不好的視覺效果,甚至當有 View 對齊於這些圖片的 ImageView 的時候,也因此產生了變化,整體就會造成佈局不平衡混亂了。

對於這個問題的解決方案之一是,通過創建繼承於 ImageView 的子類,並通過覆寫 onMeasure() 來實現固定橫縱比的佈局。常用的 support library 中的 PercentLayout 也提供了一些機制來結局這類橫縱比問題。

同樣的 ConstraintLayout 也提供了機制來專門解決這個問題,選擇想要控制橫縱比的 View 然後通過屬性視圖中修改 ratio 值來改變橫縱比,紅色圈內設置,如下所示


如上圖,我們設置的 View 組件有着向父組件的 start 和 top 邊緣的約束,它的 end 邊緣則約束向一條參考線,而 bottom 邊緣則沒有被約束,這個 View 的 layout_width 和 layout_height 都被設置成 match_constraint,表示他們會根據所有的約束來設置寬高。在佈局階段這個組件的寬度就被計算好了,但是它的高度好像沒有被確定。然後,因爲設置了寬高橫縱比,高度其實也被確定了,只是寬度的一個函數輸出值(在以上例子中橫縱比是 16:9 )

這樣設置的好處就是,當寬度變化的時候,高度自動跟着變化,如下圖通過移動這個 View 組件 end 邊緣約束向的參照線就可以看到效果。如下所示:


在 XML 中的尺寸橫縱比 DimensionRatio

上例中的 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">

    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="36dp"
        android:layout_marginTop="24dp"
        app:layout_constraintDimensionRatio="h,16:9"
        app:layout_constraintEnd_toStartOf="@+id/guideline2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@mipmap/ic_launcher" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_end="264dp" />
</android.support.constraint.ConstraintLayout>

可以發現,設置橫縱比的屬性是 app:layout_constraintDimensionRatio ,而這個值有兩個部分組成:方向和比例值。

通過上面的視圖編輯器,我們已經知道了寬度就是輸入的固定值,從而設置了方向是 h 標識了 horizontal 。其實這個方向可以不用設置,在運行時的 layout 佈局過程就可以計算推斷出來,但顯示的在 xml 源碼中聲明避免了所有可能出現模棱兩可的情況發生。在大多數情況下,這非常不必要因爲本身方向是不言自明的,就像例子中,唯有高度沒被約束,很容易推斷出來高度是根據寬度來的變量函數。

這種橫縱比的組件往往又很大的說服力,當橫縱比的權利被賦予的時候。

最後還要提到的是,上文提到的寬高屬性被設置成 match_constraint 實際上在 XML 源碼中表現是被設置成 0dp,這就像 LinearLayout 的 weight 屬性一樣,會在 XML 中設置爲 0dp ,而實際大小會根據父組件在佈局 layout 過程中的大小來決定計算出來。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章