目录
七、ConstraintLayout (约束布局)
ConstraintLayout
是一个使用“相对定位”灵活地确定微件的位置和大小的一个布局,在 2016 年 Google I/O 中面世,它的出现是为了解决开发中过于复杂的页面层级嵌套过多的问题——层级过深会增加绘制界面需要的时间,影响用户体验,以灵活的方式定位和调整小部件。
从 Android Studio 2.3起,创建layout文件就已经是默认ConstraintLayout了,但是尽管Google如此大力推这项技术,但在当时很少有人使用,近些年逐渐被大家拿起来。
与传统的布局(如 LinearLayout、RelativeLayout)相比,具有更高的灵活性和性能。它允许你在视图之间创建多种多样的约束条件,比如对齐、比例、偏移等。
7.1 布局的使用
7.1.1 位置约束
ConstraintLayout
采用方向约束的方式对控件进行定位,至少要保证水平和垂直方向都至少有一个约束才能确定控件的位置
基本方向约束
比如,我们想实现顶部和界面顶部对齐,左部和界面左部对齐。
<!-- 我的什么位置在谁的什么位置 -->
app:layout_constraintTop_toTopOf="" <!-- 我的顶部和谁的顶部对齐,例如:app:layout_constraintTop_toTopOf="parent" 表示该控件的顶部与父容器的顶部对齐;也可以是 app:layout_constraintTop_toTopOf="@id/another_view" 表示与另一个具有 id 为 another_view 的控件顶部对齐 -->
app:layout_constraintBottom_toBottomOf="" <!-- 我的底部和谁的底部对齐,比如:app:layout_constraintBottom_toBottomOf="parent" 意味着该控件的底部和父容器底部对齐,或者 app:layout_constraintBottom_toBottomOf="@id/sibling_view" 即与名为 sibling_view 的控件底部重合 -->
app:layout_constraintLeft_toLeftOf="" <!-- 我的左边和谁的左边对齐,像 app:layout_constraintLeft_toLeftOf="parent" 说明该控件的左边与父容器左边对齐,也可写成 app:layout_constraintLeft_toLeftOf="@id/left_aligned_view" 来和指定控件的左边对齐 -->
app:layout_constraintRight_toRightOf="" <!-- 我的右边和谁的右边对齐,举例:app:layout_constraintRight_toRightOf="parent" 表示该控件的右边与父容器右边对齐,还可以是 app:layout_constraintRight_toRightOf="@id/right_aligned_view" 与对应控件右边对齐 -->
app:layout_constraintStart_toStartOf="" <!-- 我的开始位置和谁的开始位置对齐,在水平方向上,对于从左到右的布局语言(如英语等),它等同于左对齐,例如:app:layout_constraintStart_toStartOf="parent" 就是和父容器的左边(开始位置)对齐;若为 app:layout_constraintStart_toStartOf="@id/start_aligned_view" 则是和指定控件的开始位置对齐,常用于适配不同语言方向下的布局一致性 -->
app:layout_constraintEnd_toEndOf="" <!-- 我的结束位置和谁的结束位置对齐,在水平方向,对于从左到右的布局语言,相当于右对齐,像 app:layout_constraintEnd_toEndOf="parent" 表示和父容器的右边(结束位置)对齐,也可以写成 app:layout_constraintEnd_toEndOf="@id/end_aligned_view" 与对应控件的结束位置重合,同样有助于多语言布局适配 -->
app:layout_constraintTop_toBottomOf="" <!-- 我的顶部位置在谁的底部位置,例如:app:layout_constraintTop_toBottomOf="@id/header_view" 意味着该控件的顶部位于名为 header_view 的控件底部,常用于布局中的上下排列关系确定 -->
app:layout_constraintStart_toEndOf="" <!-- 我的开始位置在谁的结束位置,比如:app:layout_constraintStart_toEndOf="@id/previous_view" 表示该控件的左边(开始位置)在名为 previous_view 的控件右边(结束位置),用于水平方向上的顺序排列布局 -->
app:layout_constraintBottom_toTopOf="" <!-- 我的底部位置在谁的顶部位置,像 app:layout_constraintBottom_toTopOf="@id/footer_view" 就是说该控件的底部位于名为 footer_view 的控件顶部,用于构建特定的上下层叠布局关系 -->
app:layout_constraintEnd_toStartOf="" <!-- 我的结束位置在谁的开始位置,举例:app:layout_constraintEnd_toStartOf="@id/next_view" 表示该控件的右边(结束位置)在名为 next_view 的控件左边(开始位置),方便在水平方向上精确控制控件的相邻顺序 -->
基线对齐
我们有时候需要写这样的需求:两个文本是基线对齐的。
app:layout_constraintBaseline_toBaselineOf=""
<!-- 我的文本基线和谁的文本基线对齐,常用于文本控件间的对齐,保证文字在视觉上处于同一水平线上。
例如:app:layout_constraintBaseline_toBaselineOf="@id/aligned_text_view" 可让该文本控件与指定的 aligned_text_view 的文本基线重合,使文字显示更整齐美观 -->
角度约束
有些时候我们需要一个控件在某个控件的某个角度的位置,那么通过其他的布局其实是不太好实现的。
<!-- 我的位置在以谁为圆心的圆周上的哪个角度 -->
app:layout_constraintCircle="" <!-- 指定圆形布局的中心点ID -->
app:layout_constraintCircleRadius="" <!-- 指定圆形布局的半径,单位为dp -->
app:layout_constraintCircleAngle="" <!-- 指定圆形布局的角度,取值范围0-360度,0度为右侧,顺时针增加 -->
<!-- 我与谁之间的角度关系 -->
app:layout_constraintAngle="" <!-- 指定与参考控件之间的角度,单位为度 -->
app:layout_constraintAngleReference="" <!-- 指定角度参考的控件ID -->
<!-- 我在默认角度基础上的偏移量 -->
app:layout_constraintAngleBias="" <!-- 角度偏置值,单位为度 -->
<!-- 我的某个边缘与谁的某个边缘对齐的角度 -->
app:layout_constraintStart_toAngleOf="" <!-- 我的开始边缘与谁的指定角度对齐 -->
app:layout_constraintEnd_toAngleOf="" <!-- 我的结束边缘与谁的指定角度对齐 -->
app:layout_constraintTop_toAngleOf="" <!-- 我的顶部边缘与谁的指定角度对齐 -->
app:layout_constraintBottom_toAngleOf="" <!-- 我的底部边缘与谁的指定角度对齐 -->
<!-- 我只能在指定的角度范围内移动 -->
app:layout_constraintAngleMin="" <!-- 最小角度约束,单位为度 -->
app:layout_constraintAngleMax="" <!-- 最大角度约束,单位为度 -->
<!-- 多个控件共享角度范围时的权重分配 -->
app:layout_constraintAngleWeight="" <!-- 角度权重值,用于多个控件共享角度范围时的比例分配 -->
百分比偏移
有的时候我们需要让控件在父布局的水平方向或垂直方向的百分之多少的位置。
app:layout_constraintHorizontal_bias="" <!-- 水平偏移 取值范围是0-1的小数 -->
app:layout_constraintVertical_bias="" <!-- 垂直偏移 取值范围是0-1的小数 -->
7.1.2 控件内边距、外边距、GONE Margin
ConstraintLayout
的内边距和外边距的使用方式其实是和其他布局一致的。除此之外还有GONE Margin
,当依赖的目标view
隐藏时会生效的属性。
例如B被A依赖约束,当B隐藏时B会缩成一个点,自身的margin
效果失效,A设置的GONE Margin
就会生效。
<!-- 基本外边距属性:控制控件与其他元素之间的间距 -->
android:layout_marginStart="" <!-- 控件起始边(左侧)的外边距,单位dp -->
android:layout_marginEnd="" <!-- 控件结束边(右侧)的外边距,单位dp -->
android:layout_marginTop="" <!-- 控件顶部的外边距,单位dp -->
android:layout_marginBottom="" <!-- 控件底部的外边距,单位dp -->
android:layout_margin="" <!-- 统一设置四个方向的外边距,单位dp -->
android:layout_marginHorizontal="" <!-- 同时设置左右外边距,单位dp -->
android:layout_marginVertical="" <!-- 同时设置上下外边距,单位dp -->
<!-- 约束外边距属性:在约束布局中定义相对于约束目标的外边距 -->
app:layout_constraintStart_margin="" <!-- 约束起始边(左侧)的外边距,单位dp -->
app:layout_constraintEnd_margin="" <!-- 约束结束边(右侧)的外边距,单位dp -->
app:layout_constraintTop_margin="" <!-- 约束顶部的外边距,单位dp -->
app:layout_constraintBottom_margin="" <!-- 约束底部的外边距,单位dp -->
<!-- 内边距属性:控制控件内容与控件边界之间的间距 -->
android:paddingStart="" <!-- 内容起始边(左侧)的内边距,单位dp -->
android:paddingEnd="" <!-- 内容结束边(右侧)的内边距,单位dp -->
android:paddingTop="" <!-- 内容顶部的内边距,单位dp -->
android:paddingBottom="" <!-- 内容底部的内边距,单位dp -->
android:padding="" <!-- 统一设置四个方向的内边距,单位dp -->
android:paddingHorizontal="" <!-- 同时设置左右内边距,单位dp -->
android:paddingVertical="" <!-- 同时设置上下内边距,单位dp -->
<!-- GONE状态外边距属性:当参考控件设置为GONE时的特殊外边距 -->
app:layout_goneMarginStart="" <!-- 参考控件GONE时的左侧外边距,单位dp -->
app:layout_goneMarginEnd="" <!-- 参考控件GONE时的右侧外边距,单位dp -->
app:layout_goneMarginTop="" <!-- 参考控件GONE时的顶部外边距,单位dp -->
app:layout_goneMarginBottom="" <!-- 参考控件GONE时的底部外边距,单位dp -->
<!-- 边距百分比属性:使用百分比定义边距,相对于父容器尺寸 -->
app:layout_marginStartPercent="" <!-- 左侧外边距的百分比值(0.0-1.0) -->
app:layout_marginEndPercent="" <!-- 右侧外边距的百分比值(0.0-1.0) -->
app:layout_marginTopPercent="" <!-- 顶部外边距的百分比值(0.0-1.0) -->
app:layout_marginBottomPercent="" <!-- 底部外边距的百分比值(0.0-1.0) -->
<!-- 动态边距属性:在不同布局状态下使用的边距 -->
app:layout_marginStart="@dimen/margin_small" <!-- 引用dimen资源的左侧外边距 -->
app:layout_marginEnd="@dimen/margin_large" <!-- 引用dimen资源的右侧外边距 -->
<!-- 边距偏移属性:在已有边距基础上的额外偏移 -->
app:layout_constraintHorizontal_bias="" <!-- 水平方向的边距偏移百分比(0.0-1.0) -->
app:layout_constraintVertical_bias="" <!-- 垂直方向的边距偏移百分比(0.0-1.0) -->
7.1.3 控件尺寸
尺寸限制
在ConstraintLayout
中提供了一些尺寸限制的属性,可以用来限制最大、最小宽高度。
<!-- 这些属性只有在给出的宽度或高度为wrap_content时才会生效 -->
android:minWidth="" <!-- 设置view的最小宽度 -->
android:minHeight="" <!-- 设置view的最小高度 -->
android:maxWidth="" <!-- 设置view的最大宽度 -->
android:maxHeight="" <!-- 设置view的最大高度 -->
0dp(MATCH_CONSTRAINT)
设置view
的大小除了传统的wrap_content
、指定尺寸
、match_parent
外,ConstraintLayout
还可以设置为0dp(MATCH_CONSTRAINT)
,并且0dp
的作用会根据设置的类型而产生不同的作用。
app:layout_constraintWidth_default="spread|percent|wrap"
app:layout_constraintHeight_default="spread|percent|wrap"
<!-- spread(默认):占用所有的符合约束的空间 -->
<!-- percent:按照父布局的百分比设置 -->
<!-- wrap:匹配内容大小但不超过约束限制 -->
(1)
(2)
(3)
这里写了两个控件作为对比,控件A宽度设置为wrap_content
,宽度适应内容大小,并且设置了margin
,但是显然宽度已经超过margin
的设置值了,而控件B宽度设置为0dp wrap模式
,宽度适应内容大小,并且不会超过margin
的设置值,也就是不会超过约束限制,这就是这两者的区别。
这段代码通过两个 TextView 的对比,清晰展示了 ConstraintLayout 中不同宽度约束方式的效果:
-
wrap_content + spread
:适合需要固定宽度但内容较短的场景。 -
0dp + wrap
:适合内容长度不确定,需要自适应的场景。
这种灵活的宽度约束系统是 ConstraintLayout 的核心优势之一,使开发者能够更精确地控制 UI 布局。
比例宽高(Ratio)
比例宽高(Ratio)是 ConstraintLayout 实现响应式设计的核心特性之一,尤其适合以下场景:
-
图片、视频等需要保持原始比例的媒体元素。
-
卡片、按钮等需要在不同屏幕尺寸下维持视觉比例的组件。
-
复杂列表或网格布局中的等比例元素排列。
通过灵活组合 app:layout_constraintDimensionRatio
、0dp
和权重,开发者可以轻松构建出适配各种设备的动态比例布局。
<!-- 控制控件的宽度和高度按照特定比例进行缩放 -->
<!-- 对宽高设置比例,前提是至少有一个约束维度设置为0dp,这样比例才会生效 -->
<!-- 该属性可使用两种设置: 浮点值(表示宽度和高度之间的比率),宽度:高度(表示宽度和高度之间形式的比率) -->
app:layout_constraintDimensionRatio="" <!-- 指定宽高比例 -->
7.1.4 Chains(链)
在 ConstraintLayout 中,链(Chains) 是一种强大的布局机制,用于将多个控件连接成一个组,统一管理它们的排列方式和间距。链可以是水平的(横向排列)或垂直的(纵向排列),非常适合实现均匀分布的按钮组、网格布局等场景。
<!-- 链的权重与尺寸约束结合 -->
<!-- 在链中使用0dp和权重实现更灵活的布局 -->
app:layout_constraintWidth_default="" <!-- 宽度默认值模式,可选:wrap(包裹内容)、spread(扩展到约束边界)、percent(百分比) -->
app:layout_constraintHeight_default="" <!-- 高度默认值模式,可选同上 -->
<!-- 链的样式控制 -->
<!-- 定义水平或垂直方向上元素组的排列方式 -->
app:layout_constraintHorizontal_chainStyle="" <!-- 水平链样式,可选:packed(打包)、spread(均匀分布)、spread_inside(内部均匀分布) -->
app:layout_constraintVertical_chainStyle="" <!-- 垂直链样式,可选同上 -->
<!-- 链中元素的权重分配 -->
<!-- 结合0dp使用,控制元素在链中的相对空间占比 -->
app:layout_constraintHorizontal_weight="" <!-- 水平方向权重值,用于宽度为0dp的元素 -->
app:layout_constraintVertical_weight="" <!-- 垂直方向权重值,用于高度为0dp的元素 -->
A、B、C,三个控件在水平方向上首尾互相约束,这样就形成了一条水平链,它们默认的模式是spread
,均分剩余空间。
我们还可以对水平和垂直链设置模式,模式可选的值有:spread、packed、spread_inside
。
Chains(链)
还支持weight(权重)
的配置。