一、什么是约束布局(ConstraintLayout)

  • ConstraintLayout 是一个使用“相对定位”灵活地确定微件的位置和大小的一个布局,在 2016 年 Google I/O 中面世,它的出现是为了解决开发中过于复杂的页面层级嵌套过多的问题——层级过深会增加绘制界面需要的时间,影响用户体验,以灵活的方式定位和调整小部件。
  • ConstraintLayout 是一个ViewGroup,可以在Api9以上的Android系统使用它,它的出现主要是为了解决布局嵌套过多的问题,以灵活的方式定位和调整小部件。从 Android Studio 2.3 起,官方的模板默认使用 ConstraintLayout。
  • ConstraintLayout是一个Support库,它支持向前兼容,最低可支持到API 9(android 2.3)目前app兼容性都是做到4.0以上所以ConstraintLayout的兼容性问题完全不用考虑,其本身更像是对RelativeLayout的升级,效率更高且更实用。

二、约束布局的优势

  • 搞定复杂布局
  • 减少层级嵌套
  • 提升页面性能

三、如何使用约束布局

3.1 添加依赖

首先我们需要在app/build.gradle文件中添加ConstraintLayout的依赖,如下所示。

dependencies {
    implementation "androidx.constraintlayout:constraintlayout:2.1.1"
}

3.2 位置约束

3.2.1 相对定位

ConstraintLayout采用方向约束的方式对控件进行定位,至少要保证水平和垂直方向都至少有一个约束才能确定控件的位置。

约束属性介绍:
app:layout_constraintTop_toTopOf="" 我的顶部和谁的顶部对齐

  • app:layout_constraintBottom_toBottomOf="" 我的底部和谁的底部对齐
  • app:layout_constraintLeft_toLeftOf="" 我的左边和谁的左边对齐
  • app:layout_constraintRight_toRightOf="" 我的右边和谁的右边对齐
  • app:layout_constraintStart_toStartOf="" 我的开始位置和谁的开始位置对齐
  • app:layout_constraintEnd_toEndOf="" 我的结束位置和谁的结束位置对齐
  • app:layout_constraintTop_toBottomOf="" 我的顶部位置在谁的底部位置
  • app:layout_constraintBottom_toTopOf="" 我的底部位置在谁的顶部位置
  • app:layout_constraintStart_toEndOf="" 我的开始位置在谁的结束为止
  • app:layout_constraintEnd_toStartOf="" 我的结束位置在谁的开始为止
  • app:layout_constraintLeft_toRightOf="" 我的左边和谁的右边对齐
  • app:layout_constraintRight_toLeftOf="" 我的右边和谁的左边对齐
  • app:layout_constraintBaseline_toBaselineOf="" 文本对齐(Baseline指的是文本基线)

3.2.2 角度定位

角度定位指的是可以用一个角度和一个距离来约束两个空间的中心。
有些时候我们需要一个控件在某个控件的某个角度的位置,那么通过其他的布局其实是不太好实现的,但是ConstraintLayout为我们提供了角度位置相关的属性。

  • app:layout_constraintCircle="" 目标控件id
  • app:layout_constraintCircleAngle="" 对于目标的角度(0-360)
  • app:layout_constraintCircleRadius="" 到目标中心的距离

3.3 边距

3.3.1 外边距-Margin

  • android:layout_margin="" 给每边加外边距
  • android:layout_marginStart="" 给起始边加外边距
  • android:layout_marginLeft="" 给左边加外边距
  • android:layout_marginTop="" 给上边加外边距
  • android:layout_marginEnd="" 给结束边加外边距
  • android:layout_marginRight="" 给右边加外边距
  • android:layout_marginBottom="" 给下边加外边距

# stat end (更强调位置) 的存在就是为了 Android中的RTL Layout 兼容,使用中效果大体一样

3.3.2 外边距-goneMargin

被依赖空间GONE之后生效的边距:

  • android:layout_goneMarginStart="" 当起始位置对齐的控件gone时,增加在自身起始位置的外边距
  • android:layout_goneMarginEnd="" 当结束位置对齐的控件gone时,增加在自身结束位置的外边距
  • android:layout_goneMarginLeft="" 当左对齐的控件gone时,增加在自身左边的外边距
  • android:layout_goneMarginTop="" 当顶部对齐的控件gone时,增加在自身顶部的外边距
  • android:layout_goneMarginRight="" 当右对齐的控件gone时,增加在自身右边的外边距
  • android:layout_goneMarginBottom="" 当底部对齐的控件gone时,增加在自身底部的外边距

3.3.3 内边距-padding

  • android:padding="" 给每边加内边距
  • android:paddingStart="" 给起始边加内边距
  • android:paddingLeft="" 给左边加内边距
  • android:paddingTop="" 给上边加内边距
  • android:paddingEnd="" 给结束边加内边距
  • android:paddingRight="" 给右边加内边距
  • android:paddingBottom="" 给下边加内边距

3.4 尺寸限制

在ConstraintLayout中提供了一些尺寸限制的属性,可以用来限制最大、最小宽高度,这些属性只有在给出的宽度或高度为wrap_content时才会生效,比如想给宽度设置最小或最大值,那宽度就必须设置为wrap_content,这个比较简单就不放示例代码了,具体的属性如下:

  • android:minWidth="" 设置view的最小宽度
  • android:minHeight="" 设置view的最小高度
  • android:maxWidth="" 设置view的最大宽度
  • android:maxHeight="" 设置view的最大高度

设置view的大小除了传统的wrap_content、指定尺寸、match_parent外,ConstraintLayout还可以设置为0dp(MATCH_CONSTRAINT),并且0dp的作用会根据设置的类型而产生不同的作用,进行设置类型的属性是layout_constraintWidth_default和layout_constraintHeight_default,取值可为spread、percent、wrap。具体的属性及示例如下:

app:layout_constraintWidth_default=“spread|percent|wrap”

app:layout_constraintHeight_default=“spread|percent|wrap”

spread(默认):占用所有的符合约束的空间

percent:按照父布局的百分比设置,percent模式的意思是自身view的尺寸是父布局尺寸的一定比例,该模式需要配合layout_constraintWidth_percent使用,但是写了layout_constraintWidth_percent后,layout_constraintWidth_default="percent"其实就可以省略掉了。

wrap:匹配内容大小但不超过约束限制,当控件宽度(或高度)设置为wrap_content,并且设置了margin,但是内容过长会造成margin的约束不生效,使用layout_width=“0dp”,layout_constraintWidth_default="wrap"时,内容过长不会超过margin的限制值。此方式类似于layout_width=“wrap_content”(或layout_height=“wrap_content”),app:layout_constrainedWidth=“true”(或app:layout_constrainedHeight=“true”),对控件设置了强制约束。

3.5 居中与偏移

3.5.1 居中

在RelativeLayout中,把控件放在布局中间的方法是把layout_centerInParent设为true,而在ConstraintLayout中的写法是:

app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"

意思是把控件的上下左右约束在布局的上下左右,这样就能把控件放在布局的中间了。同理RelativeLayout中的水平居中layout_centerHorizontal相当于在ConstraintLayout约束控件的左右为parent的左右;RelativeLayout中的垂直居中layout_centerVertical相当于在ConstraintLayout约束控件的上下为parent的上下。

3.5.2 偏移

  • 使用边距
  • layout_constraintHorizontal_bias 水平偏移 (0-1 ,0,则在左对齐控件的最右侧,假如赋值为1,则在右对齐控件的最左侧,假如赋值为0.5,则水平居中,假如赋值为0.3,则更倾向于左侧)
  • layout_constraintVertical_bias 垂直偏移 (类似,同上)

3.6 链(chain)

如果两个或以上控件通过下图的方式约束在一起,就可以认为是他们是一条链。
一条链的第一个控件是这条链的链头,我们可以在链头中设置 layout_constraintHorizontal_chainStyle(或layout_constraintVertical_chainStyle)来改变整条链的样式。chains提供了3种样式,分别是:

  • spread —— 展开元素 (默认),如图:
  • android 百分比 约束布局 安卓 约束布局_外边距

  • spread_inside —— 展开元素,但链的两端贴近parent,如图:
  • android 百分比 约束布局 安卓 约束布局_android 百分比 约束布局_02

  • packed —— 链的元素将被打包在一起,如图:
  • android 百分比 约束布局 安卓 约束布局_外边距_03

  • 与权重结合为权重链,约束布局设置某方向权重,需要将其layout_width=“0dp”(或layout_height=“0dp”),设置横向权重layout_constraintHorizontal_weight(constraintVertical为纵向)来创建一个权重链,如图(A控件layout_constraintHorizontal_weight=“1”,B、C控件layout_constraintHorizontal_weight=“2”):
  • android 百分比 约束布局 安卓 约束布局_控件_04

  • packed chain+bias——打包后按设置偏移
  • android 百分比 约束布局 安卓 约束布局_外边距_05

3.7 Barrier(屏障)

  • app:barrierDirection为屏障所在的位置,可设置的值有:bottom、end、left、right、start、top
  • app:constraint_referenced_ids为屏障引用的控件,可设置多个(用“,”隔开)
  • app:barrierAllowsGoneWidgets=“true/false” 定义在引用形成Barrier的视图gone时是否仍然有效
<android.support.constraint.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierAllowsGoneWidgets="true"
        app:barrierDirection="right"
        app:constraint_referenced_ids="TextView1,TextView2" />
        
    <TextView
        android:id="@+id/TextView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@+id/barrier" />

解释:在TextView1,TextView2的右侧设置了屏障,TextView3左边对齐屏障的右边。不管1、2的宽度发生如何变化,屏障永远在他们的右边,3也永远在屏障的右边(也就在1,2的右边),如图:

android 百分比 约束布局 安卓 约束布局_控件_06

3.8 Optimizer

当我们使用 MATCH_CONSTRAINT 时,ConstraintLayout 将对控件进行 2 次测量,ConstraintLayout通过设置 layout_optimizationLevel 进行优化,可设置的值有:

  • none:无优化
  • standard:仅优化直接约束和屏障约束(默认)
  • direct:优化直接约束
  • barrier:优化屏障约束
  • groups: 优化Group约束
  • chains:优化链约束
  • dimensions:优化尺寸测量

3.9 Group

Group可以把多个控件归为一组,可以方便控制一组控件显示或隐藏。
eg:

<?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="wrap_content"
    android:layout_gravity="center">

    <TextView
        android:id="@+id/tv_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="左侧"
        android:background="@color/red"
        android:padding="10dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/tv_center"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintHorizontal_bias="0.5"/>


    <TextView
        android:id="@+id/tv_center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="中间"
        android:background="@color/red"
        android:padding="10dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toEndOf="@+id/tv_left"
        app:layout_constraintEnd_toStartOf="@+id/tv_right"/>

    <TextView
        android:id="@+id/tv_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="右侧"
        android:background="@color/red"
        android:padding="10dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/tv_center"/>
    
    <androidx.constraintlayout.widget.Group
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="tv_left,tv_center"
        android:visibility="visible"/>

</androidx.constraintlayout.widget.ConstraintLayout>

效果如图:

android 百分比 约束布局 安卓 约束布局_android_07

将Group设置gone时,效果如图:

android 百分比 约束布局 安卓 约束布局_android 百分比 约束布局_08


主要是通过 constraint_referenced_ids 这个属性来实现,然后控制 Group 显示或隐藏就能同时控制加入进去的子控件。Group 使用注意事项

  • Group优先于View,下层Group优先于上层。
  • Group只可以引用当前ConstraintLayout下的View,子Layout 下的View不可以。
  • app:constraint_referenced_ids里直接写的是id的字符串,初始化后会通过getIdentifier来反射查找叫该名字的id。所以如果你的项目用了类似AndResGuard的混淆id名字的功能,切记不要混淆app:constraint_referenced_ids里的id,否则在release版本就会因找不到该id而失效。或者也可以通过代码setReferencedIds来设置id。

3.10 Placeholder

Placeholder指的是占位符,在Placeholder中可通过属性 app: content = “id” 控制对应的控 件绘制到自己的位置上,或使用setContent()设置另一个控件的id,使这个控件移动到占位符的位置。
eg:

<?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="wrap_content"
    android:layout_gravity="center">

    <TextView
        android:id="@+id/tv_right3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="右侧"
        android:background="@color/red"
        android:padding="10dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <androidx.constraintlayout.widget.Placeholder
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:content="@+id/tv_right3"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

android 百分比 约束布局 安卓 约束布局_外边距_09


若未设置app:cnotallow="@+id/tv_right3",效果如图:

android 百分比 约束布局 安卓 约束布局_android 百分比 约束布局_10

3.11 Guideline

辅助线,帮助定位控件,并不会显示在界面上。
Guildline的主要属性:android:orientation 垂直vertical,水平horizontal。

  • layout_constraintGuide_begin 开始位置(水平方向,距离顶部。垂直距离左侧)
  • layout_constraintGuide_end 结束位置(水平方向,距离顶部。垂直距离左侧)
  • layout_constraintGuide_percent 距离的百分比(水平方向,距离顶部。垂直距离左侧)
    eg:
<android.support.constraint.Guideline
        android:id="@+id/guideline1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="50dp" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

效果如图:

android 百分比 约束布局 安卓 约束布局_android_11

3.12 额外的非必要属性

下面几个属性是 UI 编辑器所使用的,用了辅助拖拽布局的,在实际使用过程中,可以不用关心这些属性。
● layout_optimizationLevel
● layout_editor_absoluteX
● layout_editor_absoluteY
● layout_constraintBaseline_creator
● layout_constraintTop_creator
● layout_constraintRight_creator
● layout_constraintLeft_creator
● layout_constraintBottom_creator

四、代码中设置约束:

通过ConstraintSet,允许在代码中进行约束设置,进行布局变换。(API 19及以上支持trasmition动画)
创建ConstraintSet对象的几种方式:

手动:

c = new ConstraintSet(); 
c.connect(....);

通过一个R.layout.xxx对象

c.clone(context, R.layout.layout1);

通过一个ConstraintLayout对象

c.clone(clayout);

布局变化开启平滑动画的方式:

TransitionManager.beginDelayedTransition(constraintLayout);

其中参数constraintLayout表示动画作用的约束布局对象。