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_weight 和 layout_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个属性:none,aligned,chain:
当 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的长度。
通过上面这些操作,我们就实现了,这个功能。