一、什么是约束布局(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 —— 展开元素 (默认),如图:
-
spread_inside —— 展开元素,但链的两端贴近parent,如图:
-
packed —— 链的元素将被打包在一起,如图:
-
与权重结合为权重链,约束布局设置某方向权重,需要将其layout_width=“0dp”(或layout_height=“0dp”),设置横向权重layout_constraintHorizontal_weight(constraintVertical为纵向)来创建一个权重链,如图(A控件layout_constraintHorizontal_weight=“1”,B、C控件layout_constraintHorizontal_weight=“2”):
-
packed chain+bias——打包后按设置偏移
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的右边),如图:
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>
效果如图:
将Group设置gone时,效果如图:
主要是通过 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>
若未设置app:content="@+id/tv_right3",效果如图:
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" />
效果如图:
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表示动画作用的约束布局对象。