ConstraintLayout(约束布局)

主要参考ConstraintLayout 属性详解 和Chain的使用
图形界面操作参考Android ConstraintLayout详解
一些注意点ConstraintLayout使用指南

Android新特性介绍,ConstraintLayout完全解析

ConstraintLayout深入系列之代替常见布局

属性整理

1、相对定位

  • app:layout_constraintLeft_toLeftOf=" "
  • app:layout_constraintLeft_toRightOf
  • app:layout_constraintRight_toLeftOf
  • app:layout_constraintRight_toRightOf
  • app:layout_constraintTop_toTopOf
  • app:layout_constraintTop_toBottomOf
  • app:layout_constraintBottom_toTopOf
  • app:layout_constraintBottom_toBottomOf
  • app:layout_constraintBaseline_toBaselineOf
  • app:layout_constraintStart_toEndOf
  • app:layout_constraintStart_toStartOf
  • app:layout_constraintEnd_toStartOf
  • app:layout_constraintEnd_toEndOf
    属性值@id/button1parent

2、约束的widget为GONE时的Margins

  • app:layout_goneMarginStart=" "
  • app:layout_goneMarginEnd
  • app:layout_goneMarginLeft
  • app:layout_goneMarginTop
  • app:layout_goneMarginRight
  • app:layout_goneMarginBottom
    属性值dp

3、倾向(Bias)

  • app:layout_constraintHorizontal_bias="0.9" (0最左边 1最右边)
  • app:layout_constraintVertical_bias (0最上边 1 最底边)
    属性值0~1

4、最小宽高(他会在 ConstraintLayout为WRAP_CONTENT时起作用。)

  • android:minWidth
  • android:minHeight

5、宽高

  • android:layout_width="wrap_content"
  • android:layout_height="wrap_content"
    属性值dp、WRAP_CONTENT 、0(就等于MATCH_CONSTRAINT)

6、比例(Ratio)

  • app:layout_constraintDimensionRatio="2:1"
  • app:layout_constraintDimensionRatio="H,2:1"
    可以以比例去定义View的宽高。
    为了做到这一点,需要将至少一个约束维度设置为0dp(即MATCH_CONSTRAINT)
    属性值:
    浮点值,表示宽度和高度之间的比率 (2,0.5)
    “width:height”形式的比例 (5:1,1:5)

7、链条样式(Chain Style)

  • app:layout_constraintHorizontal_chainStyle=" "
  • app:layout_constraintVertical_chainStyle
    属性值spread、spread_inside、packed

8、Guideline(辅助线)

  • android:orientation="vertical"
    属性值vertical、horizontal
  • app:layout_constraintGuide_begin=" "
  • app:layout_constraintGuide_end
  • app:layout_constraintGuide_percent
    属性值dp、百分比

属性介绍

相对定位 (Relative positioning)

这一节的属性和相对布局的很像,
值得注意的是参数取值是 ID(@id/button1)代表子控件和子控件之间的约束、 或者 字符串"parent" 代表子控件和父控件的约束

属性都形如layout_constraintXXX_toYYYOf
这里我的理解,constraintXXX里的XXX代表是这个子控件自身的哪条边(Left、Right、Top、Bottom、Baseline)
toYYYOf里的YYY代表的是和约束控件的哪条边 发生约束 (取值同样是 Left、Right、Top、Bottom、Baseline)

XXXYYY相反时,表示控件自身的XXX在约束控件的YYY的一侧,
例如app:layout_constraintLeft_toRightOf="@id/button1",表示的是控件自身的左侧在button1的右侧。

XXXYYY相同时,表示控件自身的XXX和约束控件的YYY的一侧 对齐,
例如:app:layout_constraintBottom_toBottomOf="parent",表示控件自身底端和父控件底端对齐。

代码为:

<Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Demo" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button2"
        app:layout_constraintLeft_toRightOf="@id/button1" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:text="button3 跳转match页"
        app:layout_constraintBottom_toBottomOf="parent" />

图示:

image.png

Margins

margin和以往的使用一致,注意margin不能为负值即可。

当约束的widget为GONE时的Margins
举例,当A控件 约束 在B控件的左边,B控件GONE了,此时A会额外拥有一个margin的能力,来“补充”B消失的导致的“位移”。

  • layout_goneMarginStart
  • layout_goneMarginEnd
  • layout_goneMarginLeft
  • layout_goneMarginTop
  • layout_goneMarginRight
  • layout_goneMarginBottom

在看Demo:

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

    <!-- android:layout_marginRight="10dp" 
    配合 app:layout_goneMarginRight="110dp"一起使用,
    在约束的布局gone时,起用goneMargin,
    但是一定要预先设置对应方向上的margin -->
    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:text="button5"
        app:layout_constraintRight_toLeftOf="@id/button4"
        app:layout_goneMarginRight="110dp"/>

当给button4 隐藏GONE掉以后:
图示:

image.png

在约束的布局gone时,控件自身的marginXXX会被goneMarginXXX替换掉

对可见性的处理(Visibility behavior)

这是对goneMargin的补充。
重点是Gone隐藏掉的控件,会被解析成一个点,并忽略margin。

ConstraintLayout能为View.Gone的View特殊处理。
通常,GONE的控件不会被显示,并且不是布局本身的一部分(即如果标记为GONE,则其实际尺寸并不会更改)。
但是在布局计算方面,GONE的View仍然是其中的一个重要区别:
对于布局传递,它们的维度将被视为零(基本上它们将被解析为一个点)
如果他们对其他小部件有约束力,那么他们仍然会受到尊重,但任何margin都将等于零,注意A的margin也被忽略了。

拿上个Demo改一下,为A 加上一个android:layout_marginRight="10dp"
为了使A 隐藏后,B仍能纹丝不动,则B的app:layout_goneMarginRight="120dp"
B goneMarginRight120= A宽度100 + AmarginRight10+B marginRight10

<Button
    android:id="@+id/button4"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    android:layout_marginRight="10dp"
    android:text="button4"
    app:layout_constraintRight_toRightOf="parent"
    />
<Button
    android:id="@+id/button5"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginRight="10dp"
    android:text="button5"
    app:layout_constraintRight_toLeftOf="@id/button4"
    app:layout_goneMarginRight="120dp"/>

居中定位和倾向(Centering positioning and bias)

居中定位
约束布局一个有用的地方是它如何处理“不可能”的约束。
比如你定义如下:

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

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</android.support.constraint.ConstraintLayout>

按照我们第一小节讲的属性值,这个定义的意思是,Button的左边和父控件的左边对齐,Button的右边和父控件的右边对齐。
可是控件是wrap_content的,它如果不铺满父控件要如何能满足这两个约束呢?
实际效果如下:

image.png

控件会居中显示,因为这两个约束作用 类似于 水平方向上,有相反的力 去拉控件,最终控件会居中显示。

倾向(Bias)
搭配bias,能使约束偏向某一边,默认是0.5,有以下属性:

  • layout_constraintHorizontal_bias (0最左边 1最右边)
  • layout_constraintVertical_bias (0最上边 1 最底边)

比如上个Demo,我加入app:layout_constraintHorizontal_bias="0.9" ,则会在水平方向上向右偏移至90%。

<android.support.constraint.ConstraintLayout
    ...
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        ...
        app:layout_constraintHorizontal_bias="0.9"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>

图示:


image.png

尺寸约束(Dimensions constraints)

ConstraintLayout的最小尺寸 (Minimum dimensions on ConstraintLayout)
可以为ConstraintLayout 自身定义最小的尺寸,他会在 ConstraintLayout为WRAP_CONTENT时起作用。

● android:minWidth
● android:minHeight

控件尺寸约束(Widgets dimension constraints)

控件的宽高有三种方式为其设置:

  • 确定尺寸
  • WRAP_CONTENT
  • 0dp,就等于MATCH_CONSTRAINT

有些人可能有疑问,为什么不用MATCH_PARENT了。
官方文档如是说:

MATCH_PARENT is not supported for widgets contained in a ConstraintLayout, though similar behavior can be defined by using MATCH_CONSTRAINT with the corresponding left/right or top/bottom constraints being set to “parent”.

意思是MATCH_PARENT不再被支持了,通过MATCH_CONSTRAINT替代。
我们写个Demo看一下三种方式设置的效果吧:

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

    <Button
        android:id="@+id/button"
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <Button
        android:id="@+id/button10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="@+id/button"
        app:layout_constraintRight_toRightOf="@+id/button"
        app:layout_constraintTop_toBottomOf="@+id/button"/>

    <Button
        android:id="@+id/button11"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="@+id/button10"
        app:layout_constraintRight_toRightOf="@+id/button10"
        app:layout_constraintTop_toBottomOf="@+id/button10"/>

</android.support.constraint.ConstraintLayout>

效果如图:


image.png

有些人是不是要说,你特么逗我,不是说好的0dp等于MATCH_CONSTRAINT,应该是撑满屏幕的呀,
OK ,把刀放下。让我们仔细看这个MATCH_CONSTRAINT属性。它match的是约束。
而这里第三个按钮的约束是第二个按钮,所以它的宽度设置为MATCH_CONSTRAINT时,是和它的约束按钮,即第二个按钮一样宽。
注意,此时,竖直方向上没有约束,所以不能使用MATCH_CONSTRAINT属性.

我们仅仅将第三个按钮的属性修改为:

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

则它宽度会撑满屏幕:


image.png

我们再修改Demo,分别为后两个按钮加上margin:

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

    <Button
        android:id="@+id/button"
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <Button
        android:id="@+id/button10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="@+id/button"
        app:layout_constraintRight_toRightOf="@+id/button"
        app:layout_constraintTop_toBottomOf="@+id/button"/>


    <Button
        android:id="@+id/button12"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        app:layout_constraintLeft_toLeftOf="@id/button10"
        app:layout_constraintRight_toRightOf="@id/button10"
        app:layout_constraintTop_toBottomOf="@id/button10"/>

    <Button
        android:id="@+id/button11"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button12"/>

</android.support.constraint.ConstraintLayout>

效果如图:


image.png

最后,记住一句话约束要和 0dp 的 方向一致。否则无效

比例(Ratio)

只有一个方向约束:
可以以比例去定义View的宽高。
为了做到这一点,需要将至少一个约束维度设置为0dp(即MATCH_CONSTRAINT)
并将属性layout_constraintDimentionRatio设置为给定的比例。

例如:

    <Button
        android:layout_width="200dp"
        android:layout_height="0dp"
        android:text="Ratio"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="2:1"
        app:layout_constraintTop_toTopOf="parent"/>

如图:


image.png

比例值有两种取值:

  • 浮点值,表示宽度和高度之间的比率 (2,0.5)
  • “width:height”形式的比例 (5:1,1:5)

当约束多于一个(宽高都被约束了)
如果两个维度均设置为MATCH_CONSTRAINT(0dp),也可以使用比例。 在这种情况下,系统会使用满足所有约束条件和比率的最大尺寸。
如果需要根据一个维度的尺寸去约束另一个维度的尺寸。
则可以在比率值的前面添加 W 或者 H 来分别约束宽度或者高度。

例如,如果一个尺寸被两个目标约束(比如宽度为0,在父容器中居中),可以使用 W 或H 来指定哪个维度被约束。

    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="H,2:1"
        app:layout_constraintTop_toTopOf="parent"/>

这里用“H”表示以高度为约束,高度的最大尺寸就是父控件的高度,“2:1”表示高:宽 = 2 : 1.
则宽度为高度的一半:


image.png

链条(Chains)

Chains 为同一个方向(水平或者垂直)上的多个子View 提供一个类似群组的概念。其他的方向则可以单独控制。Chain 的属性由该群组的第一个 View 上的属性所控制(第一个 View 被称之为 Chain head)

创建链条(Creating a chain)
如果一组小部件通过双向连接(见图,显示最小的链,带有两个小部件),则将其视为链条。

image.png

通过app:layout_constraintRight_toLeftOf="@+id/buttonB"app:layout_constraintLeft_toRightOf="@+id/buttonA"就建立了链条,(我中有你,你中有我)。
然后它们两个成了一个整体,所以链条左边设置app:layout_constraintLeft_toLeftOf="parent"使得和父控件左对齐,
右边设置app:layout_constraintRight_toRightOf="parent"使得和父控件右对齐,

链条头(Chain heads)
链条由在链的第一个元素(链的“头”)上设置的属性控制:

  • 头是水平链最左边的View,或垂直链最顶端的View

对左控件设置了margin,相当于整个链条左边有了margin

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

    <Button
        android:id="@+id/buttonA"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="50dp"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/buttonB"/>

    <Button
        android:id="@+id/buttonB"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toRightOf="@+id/buttonA"
        app:layout_constraintRight_toRightOf="parent"/>
    
</android.support.constraint.ConstraintLayout>

效果:


image.png

链条样式(Chain Style)

当在链的第一个元素上设置属性 layout_constraintHorizontal_chainStylelayout_constraintVertical_chainStyle 时,链的行为将根据指定的样式(默认为CHAIN_SPREAD)而更改。
看图这里就很像JS里的flexible有木有。因为ConstraintLayout就是模仿flexible做的。

取值如下3种:

  • spread - 元素将被展开(默认样式)
  • 加权链 - 在spread模式下,如果某些小部件设置为MATCH_CONSTRAINT,则它们将拆分可用空间
  • spread_inside - 类似,但链的端点将不会扩展
  • packed - 链的元素将被打包在一起。 孩子的水平或垂直偏差属性将影响包装元素的定位

效果大致有5种:


image.png

CHAIN_SPREAD 这个是默认的 Style, 里面的所有 View 会分散开布局
Weighted chain,在 CHAIN_SPREAD 模式下,如果有些 View 的尺寸设置为 MATCH_CONSTRAINT(0dp),则这些View 尺寸会占据所有剩余可用的空间,和LinearLayout weight类似。
CHAIN_SPREAD_INSIDECHAIN_SPREAD 类似,只不过两端的两个 View和 父容器直接不占用多余空间,多余空间在 子View 之间分散
CHAIN_PACKED这种模式下,所有的子View都 居中聚集在一起,但是可以设置bias 属性来控制聚集的位置。

如果多个子View尺寸设置为 MATCH_CONSTRAINT(0dp),则这些 View 会平均的占用多余的空间。通过layout_constraintHorizontal_weightlayout_constraintVertical_weight属性,可以控制每个View所占用的多余空间的比例。例如,对于只有两个 View的一个水平Chain,如果每个View 的宽度都设置为MATCH_CONSTRAINT, 第一个View的 weight为 2;第二个View 的weight 为 1,则第一个View所占用的空间是 第二个 View 的两倍。

Guideline

Guideline只能用于ConstraintLayout中,是一个工具类,不会被显示,仅仅用于辅助布局。
它可以是horizontal或者 vertical的。(例如:android:orientation="vertical"

  • vertical的Guideline宽度为零,高度为ConstraintLayout的高度
  • horizontal的Guideline高度为零,宽度为ConstraintLayout的高度

定位Guideline有三种方式:

  • 指定距离左侧或顶部的固定距离(layout_constraintGuide_begin
  • 指定距离右侧或底部的固定距离(layout_constraintGuide_end
  • 指定在父控件中的宽度或高度的百分比(layout_constraintGuide_percent)

一个栗子一看便知:

<?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.support.constraint.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="100dp" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="@+id/guideline"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

效果图:


image.png

推荐阅读更多精彩内容