看完才知道约束布局 ConstraintLayout 原来这么简单

前面两篇介绍了关于 ConstrantLayout 的使用和相关属性 API 的介绍,这一篇我们来实际演示一下各种情况下如何使用,所以说这一篇就是前面两篇的实践,建议你先阅读前面两篇文章。

这一篇是真正的实践,如果你看过前面两篇建议你看一下这一篇,这一篇文章会让你对前面的所学恍然大悟,融汇贯通。

创建第一个约束

我们先打开 Design 面板,然后拖动一个 ImageView 控件到画布:

拖动一个组件到画布演示 拖动一个组件到画布演示

接下来设置一个默认图片,我就设置了里面自带的默认背景图片,此时整个界面的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?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">

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/ic_launcher_background"
        tools:layout_editor_absoluteX="117dp"
        tools:layout_editor_absoluteY="243dp" />
</android.support.constraint.ConstraintLayout>

可以发现我们使用的是 layout_editor_absoluteXlayout_eidtor_absoluteY 来表示此组件在画布中的 X 和 Y 坐标的绝对位置, 你可以试着拖动一下位置,这两个值就会发生变化,如果你拖动到左上角则这两个值都会成为 0dp,甚至可以是负值。

接下来我们创建第一个约束,首先我们创建一个相对于父容器 parent 的约束,初始状态下我们的组件四周各有一个空心的圆圈,鼠标放入左右两边的圆圈内会向两边拉一个弹簧一样的线,然后圆圈变实了。

创建和父容器左右约束的示例 创建和父容器左右约束的示例

查看代码会发现此时已经没有上面的 layout_editor_absoluteX 属性了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<ImageView
    android:id="@+id/imageView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    android:layout_marginEnd="8dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:srcCompat="@drawable/ic_launcher_background"
    tools:layout_editor_absoluteY="237dp" />

但是多了几个属性,一个是 marginStartmarginEnd 另一个是 layout_constraintStart_toStartOflayout_constraintEnd_toEndOf, 这说明我们的水平约束发生了变化。

我们的画布区域有两个预览,一个是白色底,一个是蓝色底,这两个有什么区别呢?左边的白色区域是和我们之前的预览是一样的,是一个界面预览功能,而右边蓝色的部分则是表示约束关系的预览,我们可以通过上面的菜单选择开关这些预览。

关闭和打开预览功能选项 关闭和打开预览功能选项

属性区域介绍

鼠标点击在我们刚才放置的组件上面,在我们的预览视图右边有一个属性区域 Attributes 这里包含了所有可以设置的属性,其实和我们的 xml 中的配置是对应的。

声明的属性 Declared Attributes

已经声明的属性面板 已经声明的属性面板

可以看到这和我们上面的 xml 中已经声明的属性一一对应,我们可以在这里直接修改,或者去 xml 中修改,效果是一样的。

布局相关属性

这些属性主要是调整我们的 layout_widthlayout_height 以及 margin 可见性等。

组件布局相关属性面板 组件布局相关属性面板

比如我们将上面的 margin 数字 8 改为 0 则会发现 xml 中的 margin 属性消失了。滑动上面有数字 50 的滑块,会发现预览中视图会移动,其实这是改变了我们的约束偏移。

1
app:layout_constraintHorizontal_bias="0.3"

当我们选择 layout_widthmatch_constraint 则立马会变成 0dp,这个原因我们前面提到过了,因为它们是等效的,此时预览成了下图所示撑开到了父容器宽度。

match_constraint 属性演示 match_constraint 属性演示

你可以想象一下,如果我们给 ImageView 设置了 android:scaleType="fitXY" 上面会填充满整个 ImageView. 这样就更加明显了。

我们尝试着改一下 visibility 属性,会发现虽然设置了 invisible 或者 gone 但是它的约束关系一样是存在的,并不会消失,这点很重要。

设置不可见 GONE 后约束关系依然存在 设置不可见 GONE 后约束关系依然存在

直到此时我们会发现竟然还有一个报错,之前我们一直没有理会,接下来我们看看在控件树 Component Tree 这里这个报错是什么意思。

一个奇怪的报错 一个奇怪的报错

翻译一下,这句话的意思就是我们此时还没有设置竖直方向的约束,如果我们此时直接运行这些没有设置竖直方向约束的组件会直接跑到最顶端,好吧,我们来给它设置一下。我们再设置一个和父容器的顶部的约束关系:

1
app:layout_constraintTop_toTopOf="parent"

如果你这里还有其他报错,有可能是你使用了 svg 格式图片,需要在 build.gradleandroid{ defaultConfig{ } } 中添加 vectorDrawables.useSupportLibrary = true 来声明可以在工程中使用 svg 图片。

1
2
3
4
5
6
7
android {
    compileSdkVersion 28
    defaultConfig {
        //...
        vectorDrawables.useSupportLibrary = true
    }
}

本文出自水寒的个人博客,转载请说明出处:https://dp2px.com

多个组件水平排列

基于上面的知识,接下来我们来看一些比较实用和常用的布局,下面我们给里面拖放两个 <Button> 组件,然后选中这两个按钮(注意在 Blueprint 中拖动选框选中),或者按住 ctrl 选中两个按钮。右键 -> Chains -> Create Horizontal Chains. 然后给第一个按钮添加一个和父容器顶部的约束并设置为 90dp margin.

两个按钮的约束关系 两个按钮的约束关系

可以发现默认平分了剩余间隙,如果我们给任意一个 Button 设置为 match_constraint 则会填满空隙。例如我们给 Button B 设置 layout_width 属性为 match_constraint.

设置水平排了的某个组件为 match_constraint 设置水平排了的某个组件为 match_constraint

先恢复 Button B 刚刚我们设置的属性,从我们前面的文章中已经知道,这里还有 Spread insidePacked 这两种样式,我们现在需要的是实现连个按钮间距 50dp 然后整体居中,所以 Packed 比较合适。

此时我们水平链的头是 Button A,这个是默认水平排了的最左边和竖直排了的最右边,此时我们给 Button A 设置一下 chain 的模式,在右侧属性面板搜索 chain. 还有一种办法就是右键头组件,然后不断的切换 Cycle Chain modee.

搜索并设置 chain 的模式为 packed 搜索并设置 chain 的模式为 packed

设置后会发现两个按钮挤在了一起,此时我们设置 Button B 的 margin 就可以实现我们想要的效果。

实际上呢这个 Chains 就是两个组件的左右依赖关系,我们打开 xml 文件会发现,没什么特别的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Button
    android:id="@+id/button1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="90dp"
    android:text="Button A"
    app:layout_constraintEnd_toStartOf="@+id/button2"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintHorizontal_chainStyle="packed"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/imageView2" />

<Button
    android:id="@+id/button2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="50dp"
    android:text="Button B"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toEndOf="@+id/button1"
    app:layout_constraintTop_toTopOf="@+id/button1" />

也就是一个双向关系 app:layout_constraintEnd_toStartOf="@+id/button2"app:layout_constraintStart_toEndOf="@+id/button1" 这种双向关系就会形成一个 Chain, 假设我们现在需要给集中在增加一个 Button C 怎么办呢?很好办啦,直接建立 B 和 C 的类似的双向关系即可。

1
2
3
4
5
6
7
8
9
<Button
    android:id="@+id/button3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="50dp"
    android:text="Button C"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toEndOf="@+id/button2"
    app:layout_constraintTop_toTopOf="@+id/button2" />

是不是很简单呢?所以这些东西重点在于你的理解,如果你理解了就可以融汇贯通了,可以任意改变你的相关来调节布局,也可以做到布局的最优。

三个按钮的链式布局实例 三个按钮的链式布局实例

竖直的链式布局和水平的类似,这里不做讨论了,下面我们来看一个比较复杂一点的布局,来检验一下我们对约束布局的理解程度。

一个比较常见的复杂布局

之前我们遇到下面这个布局会有很多嵌套,但是我们现在使用约束布局很容易实现。

一个比较常见的复杂布局实例 一个比较常见的复杂布局实例

这个布局,实现是基于我们上面讲的链布局,我们只需要给右边的三个 <TextView> 添加竖直链,并设置为 spread_inside 模式,这样三个就居中在和图片的上下沿对齐的位置了,然后我们再来调整图片和父容器的关系以及图片和链式布局头的关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?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">

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginStart="12dp"
        android:layout_marginTop="40dp"
        android:scaleType="fitXY"
        android:visibility="visible"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_launcher_background" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="30dp"
        android:text="TextView"
        app:layout_constraintBottom_toTopOf="@+id/textView2"
        app:layout_constraintStart_toEndOf="@+id/imageView2"
        app:layout_constraintTop_toTopOf="@+id/imageView2"
        app:layout_constraintVertical_chainStyle="spread_inside" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toTopOf="@+id/textView3"
        app:layout_constraintStart_toStartOf="@+id/textView"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="@+id/imageView2"
        app:layout_constraintStart_toStartOf="@+id/textView2"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

</android.support.constraint.ConstraintLayout>

到这里我相信你已经掌握了约束布局 ConstraintLayout 的使用技巧,可以灵活应用到你的项目中去了,如果还有什么问题和疑问可以在下方留言,我会尽我的能力去帮助你的。