Android 隨筆—— 最強大的佈局 ConstraintLayout

我以前寫過一篇 ConstraintLayout 的文章如今已經到了 2018 年,最新正式版本也已經到了 1.1.2 ,又加了很多好用的特性,能夠說這個約束佈局已經成爲 Android 中最強大的佈局了,絕對不是吹噓。android

本篇文章只會講怎麼使用代碼畫布局,可視化的方式精準度方面仍是有點差強人意,若是你想了解可視化方式,請看我以前的文章。bash

讓咱們看一看這個 Android 中最強大的佈局吧!app

相對定位

1、基本用法

相對定位約束佈局最基本也最經常使用的使用方式ide

咱們先簡單看一下用法工具

<TextView
        android:id="@+id/a"
        android:layout_width="60dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:gravity="center"
        android:layout_marginTop="30dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:text="A"/>

<TextView
        android:id="@+id/b"
        app:layout_constraintTop_toTopOf="@+id/a"
        app:layout_constraintLeft_toRightOf="@+id/a"
        android:layout_width="60dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="B" />
複製代碼

咱們先看 A 控件,A 的位置怎麼來的呢? 佈局

A 控件定位

  1. app:layout_constraintTop_toTopOf="parent" A 的頂邊與 parent 的頂邊對齊
  2. app:layout_constraintLeft_toLeftOf="parent" A 的左邊與 parent 的左邊對齊

咱們看到 A 與左邊上邊都有一個空隙, 這就是普通的 android:layout_marginXXXX 屬性,我也就不細說了ui

咱們再看 B 控件,B 的位置怎麼來的呢? google

B 控件定位

  1. app:layout_constraintTop_toTopOf="@+id/a" B 的頂部與 A 的頂部對齊
  2. app:layout_constraintLeft_toRightOf="@+id/a" B 的左邊與 A 的右邊對齊

B 控件我沒有設置 margin 因此他們是貼在一塊的spa

從上面的例子咱們看到了兩個問題code

  1. 看到兩個控件 app:layout_constraintXXX_toXXXOf="xxx" 屬性的值不同,一個寫的是控件 ID,一個是 parent ,這是什麼意思呢?通常控件去約束須要一個參照物,這個參照物標識能夠是控件的 ID ,也能夠是父佈局(父容器) —— parent

就比如一根繩子一端拴在當前控件的某一位置,另外一端拴在參照物的某一個位置上,這就創建起了約束。

  1. 若是仔細看上述代碼,你們確定還有一個疑問,我明明在 A 控件上設置了 marginRight 爲何 A 和 B 仍是貼着的,這就有一個說法,若是一個邊沒有約束那麼他對應邊的 margin 是不生效的

OK 理解起來很簡單不是嗎,約束佈局最基本的語法就是 app:layout_constraint位置_to位置Of="看齊目標" 那麼像這種普通的相對定位的寫法有多少種呢,我來給大家列舉一下,再配張圖標出它們的具體位置,看完下面基本的約束佈局用法你就已經瞭解了。

控件位置展現圖

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
複製代碼

固然有一部分控件沒有 Baseline 這個位置,因此這個位置不是對每種控件都有效的

2、圓形定位
<View
        android:id="@+id/a"
        android:layout_width="120dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_red_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

<View
        android:id="@+id/b"
        android:layout_width="120dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_blue_light"
        app:layout_constraintCircle="@id/a"
        app:layout_constraintCircleAngle="45"
        app:layout_constraintCircleRadius="100dp" />
複製代碼

偷偷放一張圖片

image.png

  1. app:layout_constraintCircle 須要看齊的參照物,圖中 B 就是把 A 當作參照物進行約束的
  2. app:layout_constraintCircleAngle 要旋轉的角度,最上方 0 度,默認就是 0 度,順時針開始算。
  3. app:layout_constraintCircleRadius 兩個控件中心點的距離

約束鏈

可以在水平或垂直方向控件之間相互約束而組成的一條鏈就是約束鏈,約束鏈是由開頭的控件進行屬性控制的。沒錯就是跟着大哥走

  1. 普通的約束鏈示例
<View
        android:id="@+id/a"
        android:layout_width="80dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_red_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/b"
        app:layout_constraintTop_toTopOf="parent" />

<View
        android:id="@+id/b"
        android:layout_width="80dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_blue_light"
        app:layout_constraintLeft_toRightOf="@+id/a"
        app:layout_constraintRight_toLeftOf="@+id/c"
        app:layout_constraintTop_toTopOf="@+id/a" />

<View
        android:id="@+id/c"
        android:layout_width="80dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_green_light"
        app:layout_constraintLeft_toRightOf="@+id/b"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@+id/a" />
複製代碼

看到 A 控件就是引領小弟們的鏈頭,因此衆小弟都聽他的。咱們看到它的 app:layout_constraintHorizontal_chainStyle="packed" 屬性。 這個屬性有三種值

  • packed:控件緊挨在一塊兒。還能夠經過bias屬性設置偏移量。
  • spread:均與分佈控件。
  • spread_inside:均與分佈控件,可是兩邊控件貼邊。

我貼幾張圖大概看一下樣子

固然若是是垂直的就是 app:layout_constraintVertical_chainStyle="xxxx" 屬性

packed 樣式
spread 樣式
spread_inside 樣式

  1. 組合 layout_constraintHorizontal_bias 的約束鏈 若是鏈的樣式是 packed 咱們還能組合 layout_constraintHorizontal_bias 使用,我就不貼代碼了,直接上圖瞭解下

    packed 配合 layout_constraintHorizontal_bias

  2. 還有一種特殊的約束鏈,就是按照控件權重平分控件(明擺着搶 LinearLayout 飯碗)

<View
        android:id="@+id/a"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_red_light"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/b" />

<View
        android:id="@+id/b"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_blue_light"
        app:layout_constraintHorizontal_weight="2"
        app:layout_constraintLeft_toRightOf="@+id/a"
        app:layout_constraintRight_toLeftOf="@+id/c" />

<View
        android:id="@+id/c"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_green_light"
        app:layout_constraintHorizontal_weight="2"
        app:layout_constraintLeft_toRightOf="@+id/b"
        app:layout_constraintRight_toRightOf="parent" />
複製代碼

image.png

這種鏈就是不設置鏈的 style 而是用權重的方式進行排布。

約束佈局散亂特性

1、動態推測控件的寬或高

咱們約束佈局還支持這樣的一種狀況,咱們控件只肯定了控件的寬、高的其中一個,而後按比例算出另外一邊的寬高。(固然這個需求很小衆) app:layout_constraintDimensionRatio="2:1" 這個屬性就是按比例推測寬高的屬性

<View
        android:id="@+id/a"
        android:layout_width="100dp"
        android:layout_height="0dp"
        android:background="@android:color/holo_red_light"
        app:layout_constraintDimensionRatio="2:1"
        app:layout_constraintLeft_toLeftOf="parent" />
複製代碼

看以上代碼 2:1 其實就是 width/height = 2/1 ,height = 50dp,因此這個屬性的就是 width/height ,由此咱們只要給定寬或高就能推出另外一個。(固然若是你寬高都肯定了設置這個屬性就無效了)

這個屬性的值還能設置爲 (H,2:1),(W,2:1) 這樣的,其實我看到以後是一臉懵逼的,可是還要硬着頭皮研究,H 就是 Height,W 就是 Width,好吧說一下規律。

  1. (H,2:1) 若是肯定寬寬:高度 = 100 * 1 / 2 若是肯定高度:寬度 = 100 * 1 / 2

  2. (W,2:1) 若是肯定寬寬:高度 = 100 * 2 / 1 若是肯定高度:寬度 = 100 * 2 / 1

雖然我這樣說了可是仍是不推薦用。原本就是小衆功能若是想用仍是用普通的寫法就好了,不要帶什麼 H,W 了。裝逼太刺眼!

2、父佈局填充約束

有時候有這麼一個需求想把控件按照比例填充父佈局。Android 屏幕適配這麼複雜,好像不容易實現 這時候約束佈局有兩個屬性 layout_constraintWidth_percent layout_constraintHeight_percent 怎麼看怎麼像百分比佈局,這豈不是把一直不溫不火的百分比佈局給革命了

<View
        android:id="@+id/a"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_red_light"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintWidth_percent="0.3" />
複製代碼

填充 30% 示例
我只放這麼一段代碼你們應該就知道怎麼用了,我也就很少了說了。

默認是居中的若是想調整位置請結合 app:layout_constraintHorizontal_bias="0.3" 食用

3、聊一聊 margin 屬性

有人要說了 margin 屬性還用你說?固然了我並不會說,我要說的是 layout_goneMarginStart 沒見過吧?這個是什麼意思呢,若是要約束控件隱藏了,B 控件位置還想保持不動怎麼辦呢,那麼這是個什麼狀況呢?

  • 舉個例子 B 控件左邊相對 A 控件進行定位,並設置了 20dp 的 marginLeft。若是把 A 隱藏了會怎麼樣呢?分別看一下圖一、圖2。
    圖1
    圖2

給咱們的 B 控件加上兩個屬性

app:layout_goneMarginLeft="100dp"
app:layout_goneMarginTop="30dp"
複製代碼

成品

預想的效果很棒,不是嗎,今後咱們看出 goneMarginXXXX 就是在約束的目標控件隱藏時纔會生效的 margin。

4、WRAP_CONTENT 的小問題

若是你的控件寬或高是 wrap_content 而且控件長度過長時,他的約束會失效,咱們看個例子

<View
        android:id="@+id/a"
        android:layout_width="120dp"
        android:layout_height="50dp"
        android:background="@android:color/holo_red_light"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/b"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:background="@android:color/holo_blue_light"
        android:text="是事實是事實是事實是事實是事實"
        app:layout_constraintLeft_toRightOf="@+id/a"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/a" />
複製代碼

沒有超過邊界的例子
超過邊界約束被破壞
修復後的效果
解決方式就是使用

app:layout_constrainedWidth="true"
app:layout_constrainedHeight="true"
複製代碼
5、最小高度寬度,最大高度寬度

設置最小尺寸 layout_constraintWidth_min and layout_constraintHeight_min 設置最大尺寸 layout_constraintWidth_max and layout_constraintHeight_max

沒啥好說的。

6、控件位置偏移

app:layout_constraintHorizontal_bias="0.3" app:layout_constraintVertical_bias="0.3" 相信這兩個屬性在上面你們都看過好多遍了吧,若是控件沒有佔滿父佈局,它是能夠控制當前控件在父佈局的空間裏所佔的位置,放張圖理解一下。就跟拔河同樣哈哈

image.png

該屬性的取值範圍是 0 - 1

這是橫向的分析,豎向與這個一致

約束佈局輔助工具

1、 輔助定位線 Guideline 是一個幫助咱們來定位控件,可是他又不被用戶所感知。

<android.support.constraint.Guideline
        android:id="@+id/line"
        android:layout_width="wrap_content"
        android:orientation="vertical"
        android:layout_height="wrap_content"/>
複製代碼

上面的最基本的寫法

  1. id 是必須的否則怎麼約束
  2. android:orientation="vertical" 來控制橫向仍是豎向,用過線性佈局應該都知道這個屬性
  3. 他核心的三個屬性
  • layout_constraintGuide_begin="100dp" 距離父容器起始位置的距離
  • layout_constraintGuide_end="100dp" 距離父容器結束位置的距離
  • layout_constraintGuide_percent="0.3" 距離父容器寬度或高度的百分比,取值範圍 0 - 1

若是上述三種屬性同時出現,優先級由高到低 layout_constraintGuide_percent > layout_constraintGuide_begin > layout_constraintGuide_end

2、 控件組 Group 能夠同時控制多個控件的顯示與隱藏

<android.support.constraint.Group
              android:id="@+id/group"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:visibility="visible"
              app:constraint_referenced_ids="a,b" />
複製代碼
  1. android:visibility="visible" 控制顯示隱藏,也能夠在代碼中根據 id 獲取 Group 來控制顯示隱藏
  2. app:constraint_referenced_ids="a,b" 受這個 Group 管理的控件們的 id,, 號隔開

3、 控件屏障 Barrier

<android.support.constraint.Barrier
              android:id="@+id/barrier"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              app:barrierDirection="start"
              app:constraint_referenced_ids="button1,button2" />
複製代碼
  1. app:barrierDirection=「start」 屬性能夠控制這個屏障在哪一個位置,具體位置能夠參考文章開頭那張介紹位置的圖
  2. app:constraint_referenced_ids 屏障裏面的控件們的 id,, 號隔開

下面分析一個例子

<TextView
        android:id="@+id/tv_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="姓名:sdfsdfsdsdf"
        app:layout_constraintBottom_toBottomOf="@+id/et_name"
        app:layout_constraintTop_toTopOf="@+id/et_name"/>

<TextView
        android:id="@+id/tv_phone"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="手機號:"
        app:layout_constraintBottom_toBottomOf="@+id/et_phone"
        app:layout_constraintTop_toTopOf="@+id/et_phone"/>

<EditText
        android:id="@+id/et_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="請輸入姓名"
        app:layout_constraintLeft_toLeftOf="@+id/barrier"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

<EditText
        android:id="@+id/et_phone"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="請輸入手機號"
        app:layout_constraintLeft_toLeftOf="@+id/barrier"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_name"/>

<android.support.constraint.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="right"
        app:constraint_referenced_ids="tv_name,tv_phone"/>
複製代碼

放一張效果圖

image.png

  1. 首先把 et_name et_phone 位置放好,而後再把 tv_name tv_phone 放在屏障裏,把他們當成一個總體
  2. 而後 et_name et_phone 左邊約束咱們的屏障
  3. 爲何 et_name et_phone 對齊的會是屏障的右邊,由於 app:barrierDirection="right" 這個屬性控制的,若是改爲 left 就會變成以下的樣子,全都和屏障的左邊去對齊了

image.png

這個屏障有點稍微複雜那麼一丟丟,你們多多實踐一下

小結

OK 我在這裏寫了約束佈局的一些用法,那麼下一篇我將會繼續絮叨絮叨這個約束佈局在咱們日常開發經常使用的一些寫法和技巧!

若是還有些看不懂,請配合官方文檔食用,畢竟官方資料纔是咱們的一手資料

參考資料

官方文檔

相關文章
相關標籤/搜索