我的Android开发之旅(三):ConstraintLayout的浅入
1. 介绍
ConstraintLayout(约束布局)是在2016年 Google I/O大会所发布的一个新的布局。它能够兼容 API 9 以上的设备,它的出现是为了解决布局嵌套过多,从而降低运行性能。而使用ConstraintLayout就可以减少布局的层级结构(像我在还没有学会ConstraintLayout时,复杂的界面我一般都是用LinearLayout和RelativeLayout多层嵌套来实现的,带来了很多问题,比如:不容易阅读代码、消耗设备性能使得程序运行不那么流畅) ,相比 RelativeLayout ,更加的灵活、容易使用,配合 Android Studio 的布局编辑器,可以做到像 Visual Studio 开发 C# 的窗体应用程序一样拖拽控件来实现界面效果。
2. 如何使用ConstraintLayout
2.1 添加依赖
如果你的AndroidStudio的版本是2.3以后的版本,那么在新建项目的时候就已经给你默默的添加依赖了,如果你的版本是2.3之前或是旧项目,那么在你的app文件夹下的 build.gradle 文件中添加以下依赖即可使用:
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
2.2 相对定位
ConstraintLayout 的相对定位其实和 RelativeLayout 差不多,都是当前 View 相对于另外一个 View 的位置。这里用梅花布局来举个例子:
button1的周围4个控件都是相对于button1的上下左右,而它的代码也是和 RelativeLayout 相似,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
...>
<Button
android:id="@+id/btnCenter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1的上面"
app:layout_constraintBottom_toTopOf="@id/btnCenter"
app:layout_constraintEnd_toEndOf="@id/btnCenter"
app:layout_constraintStart_toStartOf="@id/btnCenter" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1的左边"
app:layout_constraintBaseline_toBaselineOf="@id/btnCenter"
app:layout_constraintEnd_toStartOf="@id/btnCenter" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1的右边"
app:layout_constraintBaseline_toBaselineOf="@id/btnCenter"
app:layout_constraintStart_toEndOf="@id/btnCenter" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1的下面"
app:layout_constraintEnd_toEndOf="@id/btnCenter"
app:layout_constraintStart_toStartOf="@id/btnCenter"
app:layout_constraintTop_toBottomOf="@id/btnCenter" />
</androidx.constraintlayout.widget.ConstraintLayout>
上面的例子中 app:layout_constraintBottom_toBottomOf="parent"
是表示当前View的下面相对于 parent(父布局)的下面。
app:layout_constraintStart_toStartOf="@id/btnCenter"
是表示当前View的起始处(左边)相对于 btnCenter 的起始处(左边)。
app:layout_constraintBaseline_toBaselineOf="@id/btnCenter"
是表示当前View的文本基线相对于 btnCenter 的文本基线。
其它的代码也是上述同理。
这里我们除了通过代码的方式实现效果,也可以拖拽控件实现效果,通过选中控件,拖拽View的4个约束点,约束于paren或是view的4个约束点实现:
ConstraintLayout常用的相对定位属性如下:
属性 | 作用 |
---|---|
layout_constraintLeft_toLeftOf | 当前View的左边相对另一个View的左边 |
layout_constraintLeft_toRightOf | 当前View的左边相对另一个View的右边 |
layout_constraintRight_toLeftOf | 当前View的右边相对另一个View的左边 |
layout_constraintRight_toRightOf | 当前View的右边相对另一个View的右边 |
layout_constraintTop_toTopOf | 当前View的上边相对另一个View的上边 |
layout_constraintTop_toBottomOf | 当前View的上边相对另一个View的下边 |
layout_constraintBottom_toTopOf | 当前View的下边相对另一个View的上边 |
layout_constraintBottom_toBottomOf | 当前View的下边相对另一个View的下边 |
layout_constraintStart_toEndOf | 当前View的开始处相对另一个View的结束处 |
layout_constraintStart_toStartOf | 当前View的开始处相对另一个View的开始处 |
layout_constraintEnd_toStartOf | 当前View的结束处相对另一个View的开始处 |
layout_constraintEnd_toEndOf | 当前View的结束处相对另一个View的结束处 |
layout_constraintBaseline_toBaselineOf | 当前View的文本基线相对另一个View的文本基线 |
官方文档中的图片就很好的解释了这些位置在哪里:
2.3 边距(Margin)
这里的外边距和我们平时用到外边距类似,都是当前的View距离约束的View有多远。不过需要注意的是必须先约束该控件在ConstraintLayout里的相对定位否则将不生效。使用外边距的属性还是和平时一样使用layout_margin
进行设置,这里举个例子让:
代码如下:
<androidx.constraintlayout.widget.ConstraintLayout ...>
<TextView
android:id="@+id/tvA"
...
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvB"
...
app:layout_constraintStart_toEndOf="@id/tvA"
app:layout_constraintTop_toTopOf="@id/tvA" />
<TextView
android:id="@+id/tvC"
...
app:layout_constraintStart_toEndOf="@id/tvB"
app:layout_constraintTop_toTopOf="@id/tvB" />
</androidx.constraintlayout.widget.ConstraintLayout>
那如果B的 visibility 属性设置为 Gone 呢?C还是会在原来的位置吗?接下来我们试一下看看:
可以看到C跑到刚刚B的位置去了,为什么会这样呢?我们可以通过AndroidStudio的布局编辑器中的蓝图可以看到B设置为Gone后,它的宽、高和margin都失效了,变为一个点了,但它的约束还生效,位于指定的位置,所以C依旧能相对于B的位置做变化。
那假如项目有要求,让B短暂的消失,但C还保持刚才的位置怎么办呢?有三种解决方案:
-
可以使用Invisible
-
不依赖会Gone的view
-
官方提供了以下属性:
属性 作用 layout_goneMarginStart 当依赖的View为Gone时,才会启用 layout_goneMarginEnd 同上 layout_goneMarginLeft 同上 layout_goneMarginTop 同上 layout_goneMarginRight 同上 layout_goneMarginBottom 同上
2.4 居中和偏移
2.4.1 居中
前面我说 ConstraintLayout 和 RelativeLayout 很像,那是不是也有像 layout_centerVertical
一样的属性呢? 如何居中呢?ConstraintLayout 居中的方式有所不同,它是通过相对定位的属性来实现的,具体代码如下:
<androidx.constraintlayout.widget.ConstraintLayout ...>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="居中"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="水平居中"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="垂直居中"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
怎么理解这样的写法呢?其实你可以把**约束(Constraint)**理解为一个力,app:layout_constraintStart_toStartOf="parent"
和app:layout_constraintEnd_toEndOf="parent"
就是两个力在互相拉扯View,左边和右边的力道相同,那View就居中了。
2.4.2 偏移
前面我们通过Margin属性来对View进行偏移设置,可如果我们现在想让水平居中的View向左偏移,位于1/3处呢?那么你第一反应可能会想到用layout_marginLeft
进行偏移,但是这样就有个问题,数值需要进行计算,这不又给我们开发者增加了工作量?这一点官方也想到了,所以提供了以下属性:
xml属性 | 作用 |
---|---|
layout_constraintHorizontal_bias | 水平偏移(取值范围0~1,0在左边,1在右边) |
layout_constraintVertical_bias | 垂直偏移水平偏移(取值范围0~1,0在左边,1在右边) |
那如果要让View处于在水平方向的1/3处,可以通过layout_constraintHorizontal_bias
属性设置为0.3就能实现效果:
<androidx.constraintlayout.widget.ConstraintLayout ...>
<Button
android:layout_width