Android ConstraintLayout 约束布局的使用介绍

ConstraintLayout,约束布局,兼容到Api9,可以通过托拉拽的方式来调整界面,也可以通过代码的方式(Android开发的肯定是习惯这种方式)。

在开发中,经常会有各种复杂的UI,然后,伴随着各种的嵌套,最后导致嵌套的层级太深,UI卡顿。ConstraintLayout无需任何嵌套,可以有效的减少嵌套的层级,达到UI性能优化的目的。

ConstraintLayout,作为一个新的控件,肯定会有很多的属性,方法需要我们了解。但是,它基本算是渐进式的,我们只要了解了基本的定位,就可以绘制界面了,然后,再深入的了解,相对来说,还是非常友好的。

你可以使用的约束类型:

一、相对定位

相对定位是在ConstraintLayout中创建布局的基本构建之一。只要我们了解了这些,我们就可以绘制一些简单的界面。

这些约束可以让你将给定的控件相对于另一个控件进行定位,有点类似于RelativeLayout。

你可以在横向和纵向上约束控件:

  • 横向:left,right,start,end等控制属性。
  • 纵向:top,bottom和text的baseline(基线对齐).

看下面的图
在这里插入图片描述

上面的图,
第一个图,标识出了view自身的top,bottom和text的baseline纵向的位置属性;
第二个图,标识出了left,right,start,end的横向位置属性,跟我们之前用的其他的布局一样,多了一个text的baseline对齐。

下面,我们看下可用的约束列表:

  • 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

我们看到约束列表的有很多属性。但是,主要就是left,right,top,bottom,baseline几个方向属性的控制。

我们可以把上面的所有属性都分为2个部分,看下图

在这里插入图片描述

上面,我们已经标注了2个部分的位置:

  • 第一部分,表示的就是,view自身的位置right,left,top,bottom
  • 第二部分,表示的就是,需要根据哪个view进行定位(这些toLeftOf…等等,其实跟RelativeLayout是一样的)

这里,我们举个例子,如下图,让View B在View A的右边。

在这里插入图片描述

这样,就是说,让view B自身的左边,在view A的右边。

我们知道第一个部分,表示的是自身。所以第一部分就是layout_constraintLeft。

第二部分表示的是根据哪个view来定位,在view A的右边(toRightOf,跟RelativeLayout一样)。

这样,我们就知道使用的是layout_constraintLeft_toRightOf.

<Button android:id="@+id/buttonA" ... />

<Button android:id="@+id/buttonB" ...
        app:layout_constraintLeft_toRightOf="@id/buttonA" />

这里,我们不仅可以根据另一个view定位,也可以根据ConstraintLayout(parent)来进行定位。

比如,左对齐父控件,就是自身的左边在父控件的左边。

<Button android:id="@+id/buttonB" ...
        app:layout_constraintLeft_toLeftOf="parent" />

这里有个要注意的地方,可能大家已经看到了,这些属性前面不再是android:,而是app:

到这里,我们的相对定位就说完啦,赶紧去试试吧。

二、Margins

我们知道,其他的控件,都有Margin属性,表示的就是2个控件的距离。

ConstraintLayout中,margin分为2种情况:

  • 2-1,在子view显示的时候(跟其他的布局一样)
  • 2-2,在子view隐藏的时候
2-1,子view显示的时候

这种情况,跟我们其他布局使用的时候是一样。我们先来看下margin的属性有哪些

  • android:layout_marginStart
  • android:layout_marginEnd
  • android:layout_marginLeft
  • android:layout_marginTop
  • android:layout_marginRight
  • android:layout_marginBottom

我们看到,这些属性跟其他的布局里面的一样,就是marginLeft,marginRight等等,这样,我们都已经用的很熟悉了,这里就不说了。

这里有个要注意的点:
如果,我们没有在相同方向上设置约束的话,我们设置的margin属性也不会有所用。

下面看个例子

<androidx.constraintlayout.widget.ConstraintLayout
	...>
    <TextView
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:layout_marginTop="10dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

上面,我们只设置了水平方向的约束,就是app:layout_constraintLeft_toLeftOf,layout_constraintRight_toRightOf。
但是,没有设置纵轴方向的约束(跟top,bottom相关的)。所以,上面的marginTop=10dp,其实是没有作用的。

下面看下正确的做法

<androidx.constraintlayout.widget.ConstraintLayout
	...>
    <TextView
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginTop="10dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

我们使用layout_constraintTop_toTopOf,让textview的top跟parent的top对齐,就是顶部对齐,这样,再设置marginTop就可以起作用了。水平方向上是一个道理。

2-2,在子view隐藏的时候

这里,我们看下在view隐藏的时候,有哪些Margin属性。

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

我们可以看到这个跟普通的margin属性差不多,只不过,在前面加上了gone。

它表示的就是,如果view所依赖的另一个view隐藏后,自身的Margin属性。

举个例子,如下图
在这里插入图片描述

可以看到view B是通过view A来约束的。

 <TextView
            android:id="@+id/tv_content"
            android:text="view A"
			...
            app:layout_constraintLeft_toLeftOf="parent"
            android:layout_marginLeft="10dp"/>
    <TextView
			...
            android:text="view  B"
            app:layout_constraintLeft_toRightOf="@id/tv_content"/>

这个时候,如果我们把view A设置了隐藏,那么view B就会移动到最左侧的距离(view A的Margin就不会计算了)。

如下图

在这里插入图片描述

当view A隐藏后,它身上的margin也就失效了。

这个时候,如果,我们想要view B距离左侧也有距离,并且,在view A显示的时候,它跟view A之间是没有距离的,这个时候,我们就用到了layout_goneMarginLeft的属性。

它表示,如果view A(约束的目标view)隐藏后,它的goneMarginLeft就会起作用;如果view A不隐藏,那么goneMarginLeft就不起作用。

下面,我们把view A隐藏,给view B添加这个属性

<TextView
            android:id="@+id/tv_content"
            android:layout_marginLeft="10dp"
			app:layout_constraintLeft_toLeftOf="parent"
            android:visibility="gone"
			.../>
    <TextView
			...
            android:background="#eef"
            app:layout_goneMarginLeft="20dp"
            android:text="view B"
            app:layout_constraintLeft_toRightOf="@id/tv_content"/>

我们把A隐藏了,给view B添加了goneMarginLeft=20dp属性,再来看下效果图

在这里插入图片描述
是不是达到了我们想要的效果,再用不用在代码里面来写那些判断逻辑了。

在这里插入图片描述

到这里,ConstraintLayout的margin属性就说完了。主要是两点

  • 1,普通的margin,必须添加对应方向上的约束,margin属性才能起作用
  • 2,goneMargin属性,是在约束的目标view隐藏后,起作用的属性。

三、居中定位与偏移

  • 3-1,居中
  • 3-2,Bias,偏移百分比
3-1,居中

通过上面,我们基本上可以像RelativeLayout一样的来绘制一些简单的界面了,但是好像没办法把控件居中显示啊。

我们先看一下,下面的代码

<androidx.constraintlayout.widget.ConstraintLayout
	...>
    <TextView
           
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="view A"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

这个view A,我们给他添加了(layout_constraintLeft_toLeftOf=“parent”)让view的左侧跟父view做的左侧对齐,(layout_constraintRight_toRightOf=“parent”)让view的右侧跟父view的右侧对齐

但是,view的大小是wrap_content的,并不能满足,我们的约束条件(两侧都不能到达,我们希望的位置上)。

在这种情况下,约束的作用就会将我们的view两侧平分,最终就是在父布局中居中显示,垂直居中跟这个是一个道理。

看下效果图

在这里插入图片描述

3-2,Bias,偏移百分比

使用上面的属性后,我们终于可以让view居中的显示了。但是,有时候,我们不希望view完全居中,比如,左右偏移一点?

这样,我们就需要通过Bias属性来调整view所在的位置,来达到我们想要的效果了。

  • layout_constraintHorizontal_bias :横向偏移
  • layout_constraintVertical_bias :垂直偏移

偏移的值范围是0-1之间,0表示最开始的位置(左侧或顶部),1表示结束的位置(右侧或底部)。

这个偏移的话,也是在对应方向上有约束并且是居中的约束才可以。

下面,我们给view A添加一个0.2的偏移

<androidx.constraintlayout.widget.ConstraintLayout
	...>
    <TextView
           
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="view A"
            app:layout_constraintLeft_toLeftOf="parent"
			app:layout_constraintHorizontal_bias="0.2"
            app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

看下效果图

在这里插入图片描述

这里,我们看到view A已经偏移到左侧20%的位置上,达到效果了。

四、圆形定位

版本添加:1.1。

圆形定位就是说,你可以通过一个view的中心位置,在一定角度和距离上约束另一个view的中心位置。

我们先看下,属性有哪些:

  • layout_constraintCircle : 引用目标view的id
  • layout_constraintCircleRadius : 到目标view中心的距离
  • layout_constraintCircleAngle :角度 (范围, 0 到 360)

在这里插入图片描述
上图可以看到,view B的位置是根据view A的中心位置,半径radius及角度angle来确定的。

我们直接通过代码看下

<androidx.constraintlayout.widget.ConstraintLayout ...>
	<Button
            android:id="@+id/btn_content"
            android:onClick="onPlugin"
            android:text="button A"
            	.../>
    <Button
			...
            android:background="#00f"
            android:text="button B"
            app:layout_constraintCircle="@id/btn_content"
            app:layout_constraintCircleAngle="45"
            app:layout_constraintCircleRadius="100dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述
这里可以看到,通过button A的中心,角度,和半径距离,我们确定了button B的位置。

五、尺寸约束

5-1,最大和最小的宽度,高度

你可以为ConstraintLayout约束布局定义最大和最小的尺寸大小。

  • android:minWidth 设置最小宽度
  • android:minHeight 设置最小高度
  • android:maxWidth 设置最大宽度
  • android:maxHeight 设置最大高度

当ConstraintLayout的大小设置为WRAP_CONTENT时,这些最大宽度和高度将被布局使用。

跟我们普通的布局是一样的。

5-2,view的大小约束

平通的view设置宽高(layout_width和layout_height),有3中方式,MATCH_PARENT,WRAP_CONTENT,指定具体的大小。

相较于普通的view,ConstraintLayout的子view也有3种不同的方式设置宽高(android:layout_width 和 android:layout_height)。

  • 指定具体的大小
  • 使用WRAP_CONENT
  • 使用0dp,相当于"MATCH_CONSTRAINT"

前两种方式跟其他的布局一样。关于MATCH_PARENT在ConstraintLayout里面,是不推荐使用。

我们可以使用,0dp(MATCH_CONSTRAINT)方式。然后,左右都跟parent的左右对齐就可以了。

我们用代码看下ConstaintLayout种,上面的几种情况

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout ...>

    <TextView
            android:id="@+id/tv_one"
            android:layout_width="wrap_content"
			...
            android:text="宽度:wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"/>

    <TextView
            android:id="@+id/tv_two"
            android:layout_width="0dp"
			...
            android:text="宽度:0dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"/>

    <TextView
            android:id="@+id/tv_three"
            android:layout_width="0dp"
            android:layout_marginLeft="30dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
			.../>
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述
第一个,设置了wrap_content,跟其他的布局一样。
第二个,设置了0dp,发现会填充整个父控件的宽度。
第三个,设置0dp,并且有个marginLeft。

到这里,我们知道,出了0dp,其他的方式跟普通的布局是一样的。

如果,我们想实现match_parent的效果的话,我们只需要看第二个view,设置0dp,然后,跟父控件的左右侧对齐即可(app:layout_constraintLeft_toLeftOf="parent"和app:layout_constraintRight_toRightOf=“parent”)。

下面,我们看个特殊的情况。

5-3,WRAP_CONTENT : 强制约束

添加版本:1.1。

如果尺寸设置为WRAP_CONTENT,在1.1版本之前,约束不会限制生成的大小。但是在某些情况下,你可能希望使用WRAP_CONTENT,同时继续执行约束以限制生成的尺寸。

这种情况下,你可以添加对应的属性:

  • app:layout_constrainedWidth=”true|false”
  • app:layout_constrainedHeight=”true|false”

下面,我们举个例子

<androidx.constraintlayout.widget.ConstraintLayout ...>

    <TextView
            android:id="@+id/tv_four"
			...
            android:text="A"
            app:layout_constraintRight_toRightOf="parent"/>

    <TextView
			...
            android:text="B"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@id/tv_four"/>
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

看效果,view A在布局右侧。view B位于view A的左侧并且在A和左侧屏幕的中间。我们看下代码实现。

看代码我们实现了效果,但是如果,我们的view B的内容过多时候,会是什么样呢?

在这里插入图片描述
不对啊,明明设置了B约束在A的左侧啊,为啥没有作用了呢?
我们发现view B上的约束,失效了。

这个时候,我们就需要强制约束宽度(layout_constrainedWidth),来保证B的位置,使我们的UI不变形了。

<androidx.constraintlayout.widget.ConstraintLayout ...>

    <TextView
            android:id="@+id/tv_four"
			...
            android:text="A"
            app:layout_constraintRight_toRightOf="parent"/>

    <TextView
			...
             android:text="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
            app:layout_constrainedWidth="true"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@id/tv_four"/>
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述
这里,我们看到,通过添加了layout_constrainedWidth=true后,我们的UI,终于跟我们想要的是一个效果了。

5-4,MATCH_CONSTRAINT (0dp)

添加版本:1.1。

MATCH_CONSTRAINT这种方式就是设置宽高为0dp。

当尺寸设置为MATCH_CONSTRAINT时,默认的行为就是让尺寸占据所有的可用空间。上面我们看过效果了。

再来看看还有哪些其他属性:

  • layout_constraintWidth_min and layout_constraintHeight_min :设置最小尺寸
  • layout_constraintWidth_max and layout_constraintHeight_max : 设置最大尺寸
  • layout_constraintWidth_percent and layout_constraintHeight_percent : 设置尺寸的百分比

下面我们就用百分比的属性,举个例子。

我们让子view占据父控件的一半的宽度
在这里插入图片描述
这里,可以看到,通过添加layout_ConstraintWidth_percent=0.5,我们的view的宽度就变成了父控件的一半。

5-5,百分比尺寸

要使用百分比尺寸。你需要设置一下内容:

  • 尺寸(width或height)设置成MATCH_CONSTRAINT(0dp)
  • app:layout_constraintWidth_default=“percent” or app:layout_constraintHeight_default="percent"的默认值,要设置成百分比
  • 设置layout_constraintWidth_percent or layout_constraintHeight_percent 的属性0到1之间

下面先看下效果
在这里插入图片描述
下面是代码
第一个,设置了layout_constraintWidth_default和layout_constraintWidth_percent,让控件是父控件的20%
第二个,设置了layout_constraintWidth_default属性是spread
第三个,设置了layout_constraintWidth_default属性是wrap


<androidx.constraintlayout.widget.ConstraintLayout ...>

    <TextView
            ...
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"

			app:layout_constraintWidth_default="percent"
            app:layout_constraintWidth_percent="0.2"/>

    <TextView
           	...
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"

            app:layout_constraintWidth_default="spread"/>

    <TextView
	
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"

            app:layout_constraintWidth_default="wrap"/>

</androidx.constraintlayout.widget.ConstraintLayout>

根据效果图,可以看出来layout_constraintWidth_default的属性设置

  • percent,结合layout_constraintWidth_percent属性,可以设置view的大小相对于父控件的百分比
  • spread,相当于 android:layout_width=“match_parent”
  • wrap,相当于android:layout_width=“wrap_content”
5-6,Ratio 宽高比

Ratio可以设置view的宽高比,为了让view可以限定宽高比,你最少需要把一个(layout_width或layout_height)设置为0dp(即,MATCH_CONSTRAINT)。

然后,使用layout_constraintDimensionRatio 属性。

   <Button android:layout_width="wrap_content"
                   android:layout_height="0dp"
                   app:layout_constraintDimensionRatio="1:1" />

这个将会把这个button的宽高比设置成1:1,即宽度=高度。
比率可以表示为:

  • 一个float类型的,表示宽度和高度的比率
  • "width:height"形式的比率

如果宽度和高度都设置成了MATCH_CONSTRAINT(0dp),也可以使用比率。在这种情况下,系统会设置满足所有约束下的最大尺寸,并保持宽高比。

如果要根据width来约束height,可以附加"W";如果根据height来约束width,可以附加"H";来分别约束宽度或高度。例如,width=0dp,则可以添加W用来约束宽度。H同理。

距离

    <TextView
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:background="#aaa"
            app:layout_constraintDimensionRatio="16:9"/>

这里,我们的宽高都设成了0dp。layout_constraintDimensionRatio设置的是"16:9"。

在这里插入图片描述
这个属性,对于,我们使用Banner轮播控件来说,是不是很有用处呢,再也不用在代码里面计算了。

六、链(Chains)

Chains 链在单个轴(水平或垂直)上提供了类似组的行为。在链的另一个轴上可以单独的进行约束。

6-1,创建链

如果一组控件通过双向的链接 连接在一起,则将其视为一个链。如下图

在这里插入图片描述

6-2,链头

链由在链上的第一个元素(链头)上设置的属性控制。

链头就是水平方向最左边的,垂直方向最上面的那个元素。

在这里插入图片描述

6-3,链中的Margins

如果在链接上指定了Margins,将会考虑这些margin。如果是扩展链,这些margins将会从分配的空间中去除。

6-4,链的样式

在链的第一个元素上设置属性layout_constraintHorizontal_chainStyle 或layout_constraintVertical_chainStyle时,链的行为将根据指定的样式更改(默认是CHAIN_SPREAD)。

下面看下链的样式:

  • CHAIN_SPREAD :元素将展开(默认样式)
  • Weighted chain :在 CHAIN_SPREAD 样式中, 如果有控件设置了MATCH_CONSTRAINT,它们将分了可用的空间。
  • CHAIN_SPREAD_INSIDE : 链的两端不会展开
  • CHAIN_PACKED :链的各个元素被包装到一起,子元素的水平或垂直偏移属性(bias)将影响压缩元素的位置
    在这里插入图片描述
    这面说了一堆,到现在,还没有开始写链啊。。。。。
    在这里插入图片描述

下面,我们就用CHAIN_SPREAD举个例子

<androidx.constraintlayout.widget.ConstraintLayout ...>

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

    <Button
            android:id="@+id/btn_two"
			android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="two"
			app:layout_constrainedWidth="true"
            app:layout_constraintLeft_toRightOf="@id/btn_one"
            app:layout_constraintRight_toLeftOf="@+id/btn_three"/>

    <Button
            android:id="@+id/btn_three"
			android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="three"
            app:layout_constraintLeft_toRightOf="@id/btn_two"
            app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述
这里,我们看到,给第一个view(链头)添加了layout_constraintHorizontal_chainStyle=spread这个属性,没有view都是相互约束的,这就是我们说的链。

(这里,第二个view添加了layout_constrainedWidth=true属性,不然的话,当它的内容过多时候,就会把其他两个view的位置全部占用了。有兴趣的可以试试)

6-5,链的weight

Chain链的默认样式是spread,让元素可以均匀的分布。跟上面的例子一样。

如果一个或多个元素的width或Height使用了0dp(MATCH_CONSTRAINT)的话,则它们会将剩余的空间给占用了。

如果,想让元素按照我们的想法来分配剩余的控件,那么,就需要用到属性layout_constraintHorizontal_weightlayout_constraintVertical_weight了,它会将剩余的空间,在这些使用约束控制的元素之间按照weight的值来分布(类似于LinearLayout的weight权重)。

例如,button A使用了weight=2,button B使用了weight=1,那么第一个元素占用的空间就是第二个的2倍。

看下代码

<androidx.constraintlayout.widget.ConstraintLayout ...>

    <Button
            android:id="@+id/btn_one"
			android:layout_width="0dp"
            app:layout_constraintHorizontal_weight="2"
            android:layout_height="wrap_content"
            android:text="A"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@+id/btn_two"/>

    <Button
            android:id="@+id/btn_two"
			android:layout_width="0dp"
            app:layout_constraintHorizontal_weight="1"
            android:layout_height="wrap_content"
            android:text="B"
			app:layout_constrainedWidth="true"
            app:layout_constraintLeft_toRightOf="@id/btn_one"
            app:layout_constraintRight_toLeftOf="@+id/btn_three"/>

    <Button
            android:id="@+id/btn_three"
			android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="three"
            app:layout_constraintLeft_toRightOf="@id/btn_two"
            app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

这个代码里button A 和button B把宽度都改成了0dp(MATCH_CONSTRAINT)。然后,给button A设置了weight=2,button B设置了weight=1。

所以,效果就是button A的宽度是button B的2倍。

在这里插入图片描述

6-6,margins和链

在Chain链中,使用margins时,margin是相互叠加的。

例如,在水平方向的Chain链中,如果一个view设置了10dp的右边距(marginRight),而它的下一个view设置一个5dp的左边距(marginLeft),则这两个view之间的距离是15dp。

我们添加margin属性看下

<androidx.constraintlayout.widget.ConstraintLayout ...>

    <Button
            android:id="@+id/btn_one"
			android:layout_width="0dp"

			android:layout_marginRight="20dp"

            app:layout_constraintHorizontal_weight="2"
            android:layout_height="wrap_content"
            android:text="A"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@+id/btn_two"/>

    <Button
            android:id="@+id/btn_two"
			android:layout_width="0dp"

			android:layout_marginLeft="10dp"
            
			app:layout_constraintHorizontal_weight="1"
            android:layout_height="wrap_content"
            android:text="B"
			app:layout_constrainedWidth="true"
            app:layout_constraintLeft_toRightOf="@id/btn_one"
            app:layout_constraintRight_toLeftOf="@+id/btn_three"/>

    <Button
            android:id="@+id/btn_three"
			android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="three"
            app:layout_constraintLeft_toRightOf="@id/btn_two"
            app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

我们看上面的代码,给button A添加了marginRight=20dp,给button B添加了marginLeft=10dp。

所以,效果应该是中间有30dp。看下图
在这里插入图片描述

七、虚拟辅助对象

在ConstraintLayout中,除了上面介绍的功能外,还可以在ConstraintLayout中使用虚拟的辅助对象,来帮助我们进行布局。

  • Guideline。 允许我们创建水平和垂直的line,这些Guideline相对于ConstraintLayout来摆放
  • Barrier。可以把多个带约束的控件,当做一个整体
  • Group。可以控制多个控件的可见性
  • Placeholder。可以替换指定的控件。
7-1,Guideline

Guideline,ConstraintLayout的辅助对象。它不显示在设备上(标记为View.GONE),仅用来布局,并且只在约束布局中有效。

可以通过android:orientation设置水平(horizontal )还是垂直方向(vertical)。

它的属性有:

  • layout_constraintGuide_begin。距离父容器的起始位置(左侧或者顶部)的距离。
  • layout_constraintGuide_end。距离父容器的结束位置(右侧或底部)的距离。
  • layout_constraintGuide_percent。距离父容器的宽度或者高度的百分比。

下面,用代码演示下

<androidx.constraintlayout.widget.ConstraintLayout ...>

    <androidx.constraintlayout.widget.Guideline
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:id="@+id/guideline"
            app:layout_constraintGuide_percent="0.2"
            />
    <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button A"
            app:layout_constraintLeft_toRightOf="@id/guideline"
            android:layout_marginTop="40dp"
            app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>


我们让(layout_constraintGuide_percent=“0.2”),就是Guideline在父控件的20%宽度的位置。然后,让button A在Guideline的右侧对A进行横向定位。
在这里插入图片描述

7-2,Barrier

Barrier(屏障)可以引用多个控件,作为资源。并根据指定侧最极端的控件创建虚拟的准则。

例如,一个Barrier设置右侧(barrierDirection=right),它会将所有引入的控件添加一个右侧的屏障。其他的控件,就可以根据这个Barrier来设置自己的位置。

模拟场景:登陆界面

在这里插入图片描述

左侧的label字段,用户名,密码的长度明显不一样。但是,我们想让右侧的输入框位置都是对齐的。

这种情况下,我们就需要用到Barrier了。

我们把左侧的用户名,密码放到一个Barrier里面,然后,约束Barrier的右侧就可以了。然后,让输入框都根据Barrier来约束自己就可以了。

看下代码

<androidx.constraintlayout.widget.ConstraintLayout ...">

    <TextView
            android:id="@+id/btn_one"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
			...
            android:text="用户名"/>

    <TextView
            android:id="@+id/btn_three"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
			...
            android:text="密码"
            app:layout_constraintRight_toRightOf="@+id/btn_one"
            app:layout_constraintTop_toBottomOf="@id/btn_one"/>

    <androidx.constraintlayout.widget.Barrier
            android:id="@+id/barrier"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:barrierDirection="right"
            app:constraint_referenced_ids="btn_one,btn_three"/>

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            app:layout_constraintTop_toTopOf="@id/btn_one"
            app:layout_constraintBottom_toBottomOf="@id/btn_one"
            android:text="输入姓名"
            app:layout_constraintLeft_toRightOf="@id/barrier"/>

</androidx.constraintlayout.widget.ConstraintLayout>

我们看下效果图,

在这里插入图片描述

我们可以看到,最后的TextView是根据Barrier来约束的,已经摆放到这2个label按钮的后面。

Barrier这个控件,在使用的时候,好像在XML里面是看不到效果的,需要运行到设备上才可以。

最后,如果barrier引入了隐藏的(GONE)控件,则默认也会把隐藏的控件的解析位置上创建Barrier。如果不希望考虑隐藏的控件,可以通过把属性barrierAllowsGoneWidgets 设置为false(默认是true),来达到目的。

7-3,Group

Group是控制引用控件的可见性。把控件的id通过app:constraint_referenced_ids这个属性,引用到Group上。然后,就可以给这些控件统一的设置可见性了。

多个id引入通过","逗号隔开。

<androidx.constraintlayout.widget.Group
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="visible"
            app:constraint_referenced_ids="btn_one,btn_three"/>

只需要把控件的id放到Group里面,就可以同时控制,显示/隐藏了。

这样,我们就能轻松的控制一组控件的显示/隐藏,而无需通过代码设置了。

最后,多个Group可以引用相同的控件,这种情况下,会根据Group在XML的声明顺序,来决定该控件的最终的可见性。

7-4,Placeholder

添加版本:1.1。

作用:当Placeholder上设置了另一个view的id,通过(setContent()),这个Placeholder就成为了有内容的view。如果该内容view存在屏幕上,则将其视为已离开其原始位置。

内容view是使用Placeholder的参数来进行定位的(跟其他view一样,该Placeholder也受布局约束)。

下面看下代码,现在界面上画一个button,如下

<androidx.constraintlayout.widget.ConstraintLayout ...>

    <Button 
			...
            android:text="placeholder"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

效果
在这里插入图片描述

我们看到,在屏幕的右上角,有个button在静静的呆着。。。。

下面,我们加入Placeholder。

<androidx.constraintlayout.widget.ConstraintLayout ...>

    <Button 
			...
            android:text="placeholder"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

    <androidx.constraintlayout.widget.Placeholder
           	...
			app:content="@id/btn_placeholder"

            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

通过content="@id/btn_placeholder"属性,我们把button的id设置给了Placeholder。Placeholder是水平和垂直都居中的。

效果如下
在这里插入图片描述

我们发现右上角的button,跑到了屏幕的中间,占用了Placeholder的位置,使用的是Placeholder的约束属性。

八、Flow

版本2.0后,添加。
Flow类似于FlowLayout,FlexBoxLayout,流式布局
主要的属性,有4个:

  • constraint_referenced_ids
    需要约束的view的id合集,通过逗号分隔
  • orientation
    这个跟LinearLayout的横向,纵向是一样的,加载流式布局的方向
  • flow_horizontalGap / flow_verticalGap
    水平或垂直方向上 流式布局中Item的间距
  • flow_wrapMode
    流式布局中,排列的方式

这里以横向流式布局为例,下图展示了flow_wrapMode种的3个属性:nonealignedchain

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

flow_wrapMode 的属性为chain,aligned时,可以通过 flow_maxElementsWrap,来控制Flow的每行/列,显示的最大元素数量。

下图所示,就是flow_maxElementsWrap=“3”,每行最多3个元素
在这里插入图片描述

最后,Flow的完整示例代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">

    <androidx.constraintlayout.helper.widget.Flow
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="#CCC"
        app:constraint_referenced_ids="iv_one,iv_two,iv_three,iv_four,iv_five,iv_six,iv_seven"
        app:flow_horizontalGap="10dp"
        app:flow_verticalGap="10dp"
        app:flow_wrapMode="chain"
        app:flow_maxElementsWrap="3"
        android:orientation="horizontal"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/iv_one"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_two"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_three"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_four"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_five"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_six"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_seven"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@drawable/icon_share_circle" />
</androidx.constraintlayout.widget.ConstraintLayout>

CircularFlow

版本:2.0后添加。这个看名字就知道是圆形的流失布局。

它有3个重要属性:

  • constraint_referenced_ids
    约束id的合集。需要围绕中心view排列的view的合集
  • circularflow_viewCenter
    需要位于中心的view的id。
  • circularflow_angles
    上面那些ids集合中view跟中心view的角度
  • circularflow_radiusInDP
    上面那些ids集合中view 中心点距离中间 view中心点的距离

在这里插入图片描述

上面是一个实现的简单的CircularFlow。

<androidx.constraintlayout.widget.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">

    <androidx.constraintlayout.helper.widget.CircularFlow
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="#CCC"
        app:constraint_referenced_ids="iv_two,iv_three,iv_four,iv_five,iv_six"
        app:flow_horizontalGap="10dp"
        app:circularflow_viewCenter="iv_one"
        app:circularflow_angles="90,135,180,270,0"
        app:circularflow_radiusInDP = "90,100,110,120,100"
        app:flow_verticalGap="10dp"
        app:flow_wrapMode="chain"
        android:orientation="horizontal"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <ImageView
        android:id="@+id/iv_one"
        android:layout_width="80dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_height="80dp"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_two"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#AAA"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_three"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:background="#DDD"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_four"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#CCC"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_five"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#BBB"
        android:src="@drawable/icon_share_circle" />

    <ImageView
        android:id="@+id/iv_six"
        android:layout_width="80dp"
        android:background="#AAA"
        android:layout_height="80dp"
        android:src="@drawable/icon_share_circle" />

</androidx.constraintlayout.widget.ConstraintLayout>

九、Layer

版本2.0后,添加
Layer也是2.0之后,出来的一个添加图层的View
他本身是不需要约束的,它会根据它内部constraint_referenced_ids中,那些view的id来进行约束(后面有完整的示例代码)。
如下图,当文本的宽度全屏时,layer的背景色就填充了整个宽度;当文本的宽度是wrap的时候,layer的背景层也只是包裹住内容。

在这里插入图片描述
在这里插入图片描述
到这里,我们有个疑惑,同样的功能。我们给这2个view添加一个ViewGroup同样可以实现;不过,这样的话,就会多嵌套了一层,ConstraintLayout的出现就是为了避免嵌套问题的。

下面,看下完整的xml布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">

    <androidx.constraintlayout.helper.widget.Layer
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="#CCC"
        app:constraint_referenced_ids="text,image"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#F00"
        android:text="我是一个小文本"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/image"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:src="@drawable/icon_share_circle"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text" />
</androidx.constraintlayout.widget.ConstraintLayout>

十、优化

在1.1中,公开了约束的优化。我们可以通过把属性app:layout_optimizationLevel添加到ConstraintLayout来优化。

属性的可选项有:

  • none。不优化
  • standard。默认的,仅优化直接约束和Barrier约束
  • direct。优化直接约束
  • barrier。优化Barrier(屏障)约束
  • chain。优化链约束
  • dimensions。优化尺寸约束。

最后,在2.0,还加入了Layer,自定义Helper,Flow等控件。

十一、注意事项

9-1,Margin的使用

在相应的margin方向属性上,必须有约束。
比如,设置marginLeft,view必须在水平方向上有约束,否则,不起效果

9-2,wrap_content的使用

如果view使用的width或者height是wrap_content的问题,如果数据量过大,可能会早晨UI的错乱。
可以使用constraintWidth=true来限制。

9-3,特殊效果实现

之前,UI有个需求,如下图

在这里插入图片描述

需求:
标题显示:搜索内容,搜索的数量,还有一个图片Icon。

如果搜索标题太长的话,显示"…"。但是,搜索的数量跟Icon不能受影响。


下面,我们就通过ConstraintLayout来实现左边的内容,不考虑Icon。

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                                   xmlns:app="http://schemas.android.com/apk/res-auto"
                                                   android:layout_weight="1"
                                                   android:layout_width="match_parent"
                                                   android:layout_height="wrap_content">

    <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            app:layout_constraintHorizontal_chainStyle="packed"
            android:text="流水流水流水流水流水流水流水流水"
            android:textSize="30sp"
            app:layout_constrainedWidth="true"
            app:layout_constraintHorizontal_bias="0"
            android:textStyle="bold"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@+id/tv_title_tips"/>

    <TextView
            android:id="@+id/tv_title_tips"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:layout_marginLeft="20dp"
            android:text="共100个"
            app:layout_constraintBaseline_toBaselineOf="@id/tv_title"
            app:layout_constraintLeft_toRightOf="@id/tv_title"
            app:layout_constraintRight_toRightOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>


实现思路:

1,先把2个view设置成链(Chain),然后设置链头(第一个view)的样式是"packed",让2个view连在一块(layout_constraintHorizontal_chainStyle=“packed”)。

2,设置bias偏移(layout_constraintHorizontal_bias=“0”),让它靠近父控件的左侧。

3,设置强制约束(layout_constrainedWidth=“true”),来限制第一个view的长度。

通过上面这些操作,我们就实现了,这个功能。

在这里插入图片描述
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值