ConstraintLayout 介紹

不知道從什么時候開始,Android 死丟丟已經默認使用約束布局 ConstraintLayout 作為默認布局了,但是懶癌發作一直不想學習,每次都換成 LinearLayout,這次也忘記了為啥開始學習這個東西,學完發現還挺爽……寫個筆記記錄一下,哈哈

正文開始~

相對布局

屬性集合

類似 RelativeLayout ,使用相對位置的屬性來互相約束位置。具體的屬性以及使用方式也類似 RelativeLayout,默認像 FrameLayout 一樣堆疊在一起,使用屬性講層級關系區分開:

layout_constraintLeft_toLeftOf   當前控件的左側與某個控件的左側對齊
layout_constraintLeft_toRightOf  當前控件的左側與某個控件的右側對齊
layout_constraintRight_toLeftOf  當前控件的右側與某個控件的左側對齊
layout_constraintRight_toRightOf 當前控件的右側與某個控件的右側對齊

layout_constraintStart_toEndOf   同上
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf

layout_constraintTop_toTopOf       當前控件與某個控件頂端對齊
layout_constraintTop_toBottomOf    即當前控件某個控件的下面
layout_constraintBottom_toTopOf    即當前控件在某個控件的上面
layout_constraintBottom_toBottomOf 當前控件與某個控件底部對齊

layout_constraintBaseline_toBaselineOf 文本基線對齊
具體示例
<!-- 居中對齊實現方式 -->

<!-- 上下左右全部受 parent 約束,最后的效果就是「居中對齊」 -->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="居中對齊"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"/>

<!-- 同理,左右受 parent 約束,效果就是「水平居中對齊」-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="水平居中對齊"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"/>

<!-- 同理,上下受 parent 約束,效果就是「垂直居中對齊」-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="垂直居中對齊"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"/>

居中對齊很好理解,下邊我們來寫一個正常的 item 布局看看:

ConstraintLayout 實現的 item 布局
<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="wrap_content"
    android:padding="5dp">

    <ImageView
        android:id="@+id/iv_logo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@mipmap/ic_launcher" />

    <!-- 設置標題名稱 View 的左側邊緣位于 logo 的右側 -->
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="蝦吃蝦涮(華貿店)"
        app:layout_constraintLeft_toRightOf="@+id/iv_logo"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- 設置價格 View 的底部靠近父布局,且頂部參考 titleView,同時左側與 titleView 對齊 -->
    <TextView
        android:id="@+id/tv_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="¥64/人"
        android:textSize="13sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="@id/tv_title"
        app:layout_constraintTop_toBottomOf="@id/tv_title" />

    <!-- 設置 distanceView 緊貼屏幕右側,且頂部與 priceView 對齊-->
    <TextView
        android:id="@+id/tv_distance"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1.1km"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/tv_price" />

    <!-- 設置 areaView 頂部與左側都參考 priceView,底部位置參考 ivLogo-->
    <TextView
        android:id="@+id/tv_area"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="朝陽區 大望路"
        android:textSize="13sp"
        app:layout_constraintBottom_toBottomOf="@id/iv_logo"
        app:layout_constraintLeft_toLeftOf="@id/tv_price"
        app:layout_constraintTop_toBottomOf="@id/tv_price" />

    <!-- 設置 hotView 緊貼屏幕右側,且頂部與 areaView 頂部對齊-->
    <TextView
        android:id="@+id/tv_curr_hot"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="當前人氣89"
        android:textSize="13sp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/tv_area" />

    <!-- dividerView 位于整個布局的最底部,且始終位于 ivLogo 底部,并保持一定距離 -->
    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:layout_marginTop="7dp"
        android:background="@android:color/darker_gray"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv_logo" />

</android.support.constraint.ConstraintLayout>

可以發現使用約束布局實現比普通的 RL 實現還要簡單,界面完全實現扁平化,沒有任何嵌套。如果使用 LL 或者 RL 來實現同樣的效果,代碼要復雜多少想必不用我多說。

Bias 偏向

以上的內容就是基本使用了,把上下左右各種參考、依賴關系搞明白,本身沒有多么復雜,使用起來也和 RL 差不多,下面來介紹一些新花樣。

bias 很好理解,正如其英文本意一樣,它表達的是偏移。當某一布局同時受兩個相反方向的約束力時,該布局就會處于約束它的那兩個力量的正中央。而 layout_constraintHorizontal_biaslayout_constraintVertical_bias 就是用在這種時候,用來將某一方向的約束力減弱。來自兩側的約束力可以為 parent,也可以是普通 View。

文字描述可能有點抽象,具體布局文件還是更好理解一些:

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="this is a text"
        app:layout_constraintLeft_toLeftOf="parent" />

    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="this is a text"
        app:layout_constraintHorizontal_bias="0.3"
        app:layout_constraintLeft_toRightOf="@id/tv1"
        app:layout_constraintRight_toLeftOf="@id/tv3" />

    <TextView
        android:id="@+id/tv3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="this is a text"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>

布局很簡單,三個 TextView 并排顯示,左右兩個分別緊貼父布局,中間一個受左右兩側布局約束,本來應該是位于兩個 TextView 正中央,但是由于設置了 layout_constraintHorizontal_bias 小于 0.5,所以最后效果中間的 TextView 整體偏向左側,展示如下圖:

image

Circle 布局

這個看上去很厲害的!可以令 B 布局以 A 布局為圓心,然后用角度和半徑距離來約束兩個布局的位置。廢話不多說,上圖:

image

這個也很好理解,主要有三個屬性:

layout_constraintCircle :      當前布局以哪個布局為圓心
layout_constraintCircleRadius :半徑
layout_constraintCircleAngle : 擺放角度

我反正是給谷歌跪了……

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/tv_center"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:text="Circle Center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
    <!-- 「測試布局」以 tv_center 為圓心,位于其 135° 方向的 100dp 處 -->
    <TextView
        android:text="測試布局"
        app:layout_constraintCircle="@id/tv_center"
        app:layout_constraintCircleAngle="135"
        app:layout_constraintCircleRadius="100dp"
        android:textColor="@android:color/black"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>

替代 MATCH_PARENT 的 MATCH_CONSTRAINT

在約束布局中,由于布局受各方約束控制,也就沒有所謂的「match_parent」了。隨之而來的需求則是,左邊有個布局約束我,右邊還有個布局約束我,然后我就想充滿剩余的全部位置,「match_constraint」也就應運而生了。

說起來復雜,其實只需要把對應的 View 寬高設置為 0dp 即可,該 View 就會占據上剩余的所有可用空間。在這種情況下,谷歌給我們提供了幾個額外的屬性:

layout_constraintWidth_min   寬度最小值
layout_constraintHeight_min  高度最小值

layout_constraintWidth_max   寬度最大值
layout_constraintHeight_max  高度最大值

layout_constraintWidth_percent   寬度占剩余位置的百分比
layout_constraintHeight_percent  高度占剩余位置的百分比

具體示例如下:

<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">

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Test Button"
        app:layout_constraintLeft_toLeftOf="parent" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintRight_toRightOf="parent" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constrainedWidth="true"
        android:text="Button"
        app:layout_constraintWidth_min="wrap"
        app:layout_constraintWidth_max="wrap"
        app:layout_constraintWidth_percent="0.3"
        app:layout_constraintLeft_toRightOf="@id/btn1"
        app:layout_constraintRight_toLeftOf="@id/btn2" />

</android.support.constraint.ConstraintLayout>
image

Chains 鏈

如果幾個不同的 View 兩兩發生關聯,如下圖,則這幾個 View 構成了一個 Chains(鏈)。

image

具體布局代碼如下:

<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">

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/btn2" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toRightOf="@id/btn1"
        app:layout_constraintRight_toLeftOf="@id/btn3" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toRightOf="@id/btn2"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>

這樣這三個 Button 就形成了一個橫向的 Chain,在這個鏈的最左側的元素成為鏈頭,我們可以在其身上設置一些屬性,來決定這個鏈的展示效果:

該屬性為:

layout_constraintHorizontal_chainStyle
layout_constraintVertical_chainStyle

其取值可以為:spread、spread_inside、packed。

具體樣式展示如下:

  1. spread,基本上就是按照權重等分
image
  1. spread_inside,也是等分展示,但是兩側吸附

    image
  2. packed,整條鏈擠在一起,居中展示

    image

官網有一個圖來展示不同樣式的 Chains,可以參考一下,也很形象:

image

虛擬輔助視圖

與以往的 ViewGroup 不同,ConstraintLayout 還提供了幾種輔助頁面繪制的布局,這種布局一般表現為引導線之類,不會在頁面上繪制,但是可以通過占位的方式,成為不同布局的約束條件。

GuideLine

顧名思義,GuideLine 可以創建基于父布局 ConstraintLayout 的水平或者垂直準線,從而幫助開發者進行布局定位。

這個布局有四個基本屬性,依次為:

orientation 如上所述,用來表示是垂直方向還是豎直方向
layout_constraintGuide_begin 距離父親的起始位置
layout_constraintGuide_end 距離父親的結束位置
layout_constraintGuide_percent 距離父親的位置,用百分比表示

經過試驗,percent 優先級最高,其次是 begin,最后是 end,一般來講使用 percent 就足夠了

xml 以及對應的頁面效果如下:

<android.support.constraint.ConstraintLayout                
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 假設說現在需要將 ImageView 擺放到右下角的位置,就可以使用 GL 輔助實現-->
    <android.support.constraint.Guideline
        android:id="@+id/gl_vertical"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.8"/>

    <android.support.constraint.Guideline
        android:id="@+id/gl_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.8" />

    <ImageView
        android:src="@mipmap/ic_launcher"
        app:layout_constraintLeft_toRightOf="@id/gl_vertical"
        app:layout_constraintTop_toBottomOf="@id/gl_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>
image
Barrier

與 GuideLine 差不多,但是比它更靈活,可以用來約束多個布局,且自動匹配最大最小值進行約束。

Barrier 有兩個基本屬性:

  • barrierDirection

    取值可為 top, bottom, left, right, start, end

    用于約定柵欄攔截的 View 方向,假設說要攔截的 View 在右側,這個屬性就應該為 right 或者 end

  • constraint_referenced_ids

    被柵欄保護,屏蔽起來的 View 集合,直接輸入 viewId,用逗號分隔即可;barrier 會根據寬度或者高度最大的那個 View 來設置柵欄的邊界

可能會有些抽象,我們在開發時可能會遇到一種比較蛋疼的需求:

姓名、性別、出生日期、手機號等字段從上到下一字排開,但是每個字段對應的值要保證彼此左側對齊

講道理以前這種布局我一直不知道怎么畫,但是現在有了 barrier 以后這問題就迎刃而解了。我們可以用 barrier 將左側的那些字段與右側的值攔截開,barrier 會自動識別最寬的那個字段,并將之作為 barrier 的寬度,之后每個值都用 barrier 來制造約束就可以了。

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="姓名:"/>

    <TextView
        android:id="@+id/tv_gender"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/tv_name"
        android:text="性別:"/>

    <TextView
        android:id="@+id/tv_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/tv_gender"
        android:text="手機號:"/>

    <TextView
        android:id="@+id/tv_birthday"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/tv_phone"
        android:text="出生日期:"/>

    <android.support.constraint.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="end"
        app:constraint_referenced_ids="tv_name,tv_phone,tv_gender,tv_birthday"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@id/barrier"
        android:text="易烊千璽"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/tv_name"
        app:layout_constraintLeft_toRightOf="@id/barrier"
        android:text="男"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@id/barrier"
        app:layout_constraintTop_toBottomOf="@id/tv_gender"
        android:text="13800138000"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@id/barrier"
        app:layout_constraintTop_toBottomOf="@id/tv_phone"
        android:text="2000年1月1日" />

</android.support.constraint.ConstraintLayout>

顯示效果如圖,很完美有沒有?

image
Group

Group 是一個組,用來批量控制 View 的顯示與隱藏;但是注意這不是個 ViewGroup,它只是一個不執行繪制的 View,和 barrier 一樣,它有一個 constraint_referenced_ids 的屬性,可以將需要隱藏的 ViewId 丟進去,在需要的時候將其批量隱藏即可。

還通過上面的例子,假設現在要把性別一欄隱藏掉:

<android.support.constraint.Group
    app:constraint_referenced_ids="tv_gender,tv_sex_value"
    android:visibility="gone"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

通過將性別的 key 和 value 的 id 都放進去,將其設置為 gone,則可以將該組實現隱藏:

image

但是,使用 Group 控制可見性是有坑的:

  1. 和以前用的ViewGroup有一點不同,以前用ViewGroup約束View的時候,外層ViewGroup設置成可見,里層View設置成不可見是可以生效的,但是用Group就不能。Group約束的元素的可見性始終一致。

  2. 調用Group的setVisibility方法不會立即對它約束對子View生效,而是要等到Group所在的ConstrainLayout調用preLayout方法時才會生效。preLayout只有在第一次layout和布局發生變化時才會調用。

Optimizer優化

可以通過將標簽app:layout_optimizationLevel元素添加到 ConstraintLayout 來決定應用哪些優化

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_optimizationLevel="standard|dimensions|chains"/>
  • none: 不執行優化
  • standard: 默認,僅優化直接和障礙約束
  • direct: 優化直接約束
  • barrier: 優化障礙約束
  • chain:優化鏈條約束
  • dimensions:優化維度測量,減少匹配約束元素的度量數量

參考文章

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容