概述

ConstraintLayout 是 Android 官方推出的布局管理器,通过**约束(Constraint)**描述每个 View 的位置关系,实现扁平化布局结构,解决传统布局(LinearLayout、RelativeLayout)的嵌套性能问题。

设计目标

  • 减少布局嵌套:降低视图层次结构的复杂性
  • 提升性能:减少测量和布局的复杂度
  • 灵活的定位系统:支持复杂的响应式布局
  • 可视化编辑友好:Android Studio 布局编辑器的主要支持布局

核心思想

每个 View 有 4 条边和一个基线:

            top
        +-----------+
 start  |           |  end
(left)  |  baseline |  (right)
        |-----------+
        +-----------+
           bottom

基本规则:每个 View 至少需要一个水平约束 + 一个垂直约束,否则运行时会跑到 (0,0)。

约束目标可以是:

  • Parent:父容器的边缘
  • Other View:其他视图的边缘
  • Guideline:辅助线
  • Barrier:屏障

基础位置约束

1. 约束语法

app:layout_constraint[自己的边]_to[对方的边]Of="[目标ID或parent]"

2. 完整约束方向

<!-- 水平方向 -->
app:layout_constraintStart_toStartOf      <!-- 左边对齐左边 -->
app:layout_constraintStart_toEndOf        <!-- 左边挨着右边(在它右侧) -->
app:layout_constraintEnd_toStartOf        <!-- 右边挨着左边(在它左侧) -->
app:layout_constraintEnd_toEndOf          <!-- 右边对齐右边 -->
 
<!-- 垂直方向 -->
app:layout_constraintTop_toTopOf          <!-- 顶部对齐 -->
app:layout_constraintTop_toBottomOf       <!-- 在它下方 -->
app:layout_constraintBottom_toTopOf       <!-- 在它上方 -->
app:layout_constraintBottom_toBottomOf    <!-- 底部对齐 -->
 
<!-- 基线对齐(文字对齐很有用) -->
app:layout_constraintBaseline_toBaselineOf

3. 相对于 parent

<!-- 四边都约束到 parent → 居中 -->
<View
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent" />
+--------------------+
|                    |
|      [View]        |
|                    |
+--------------------+

4. 相对于其他 View

<!-- A 左上角 -->
<TextView android:id="@+id/A"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
 
<!-- B 在 A 右边,顶部对齐 -->
<TextView android:id="@+id/B"
    app:layout_constraintStart_toEndOf="@id/A"
    app:layout_constraintTop_toTopOf="@id/A" />
 
<!-- C 在 A 下面,左边对齐 -->
<TextView android:id="@+id/C"
    app:layout_constraintStart_toStartOf="@id/A"
    app:layout_constraintTop_toBottomOf="@id/A" />
+----------------------+
| [A] [B]              |
| [C]                  |
+----------------------+

尺寸控制

1. 三种宽高模式

含义说明
固定值(如 100dp固定尺寸与常规相同
wrap_content包裹内容与常规相同
0dp匹配约束(MATCH_CONSTRAINT)根据约束自动计算尺寸

重点:0dp 不是”0像素”,而是”让约束来决定宽度”

<!-- 填满整个父布局宽度 -->
<TextView
    android:layout_width="0dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent" />
 
<!-- 从 A 右边到父布局右边 -->
<TextView
    android:layout_width="0dp"
    app:layout_constraintStart_toEndOf="@id/A"
    app:layout_constraintEnd_toEndOf="parent" />

注意:不推荐在 ConstraintLayout 中使用 match_parent,用 0dp + 约束代替。

2. 宽高比(Aspect Ratio)

<ImageView
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintDimensionRatio="16:9"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
<!-- 宽度填满,高度按 16:9 自动计算 -->

指定以哪边为基准:

app:layout_constraintDimensionRatio="H,16:9"   <!-- 以宽度为基准,计算高度 -->
app:layout_constraintDimensionRatio="W,16:9"   <!-- 以高度为基准,计算宽度 -->

3. 最大/最小尺寸与百分比

android:layout_width="0dp"
app:layout_constraintWidth_min="100dp"       <!-- 最小宽度 -->
app:layout_constraintWidth_max="300dp"       <!-- 最大宽度 -->
app:layout_constraintHeight_max="300dp"      <!-- 最大高度 -->
 
<!-- 百分比 -->
app:layout_constraintWidth_percent="0.5"     <!-- 占 parent 宽度的 50% -->
app:layout_constraintHeight_percent="0.6"    <!-- 占 parent 高度的 60% -->

4. 限制 wrap_content

wrap_content 默认内容多大就多大,可能超出约束范围。加 constrainedWidth 限制:

android:layout_width="wrap_content"
app:layout_constrainedWidth="true"     <!-- 内容再长也不超出约束范围 -->

Bias(偏移)

当一个 View 两侧都有约束时,默认居中(bias=0.5)。用 bias 调整位置,取值 0~1

app:layout_constraintHorizontal_bias="0.3"   <!-- 水平方向,0=最左,1=最右 -->
app:layout_constraintVertical_bias="0.2"     <!-- 垂直方向,0=最上,1=最下 -->
bias=0       bias=0.3      bias=0.5      bias=1
[V]........  ...[V].....   .....[V]....  .........[V]

示例:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.3" />  <!-- 水平位置偏向左边30% -->

Margin 和 Gone Margin

1. 普通 margin

和其他布局一样,但 margin 只在有约束的方向上生效。没有 constraintEnd 就设 marginEnd 是无效的。

android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"

2. Gone Margin(ConstraintLayout 独有特性)

当约束目标变成 GONE 时使用的 margin:

app:layout_goneMarginStart="0dp"
app:layout_goneMarginTop="0dp"
app:layout_goneMarginEnd="0dp"
app:layout_goneMarginBottom="0dp"

场景:A 在 B 左边,间距 16dp。当 A 隐藏后,B 不希望左边还空 16dp:

<TextView android:id="@+id/B"
    app:layout_constraintStart_toEndOf="@id/A"
    android:layout_marginStart="16dp"
    app:layout_goneMarginStart="0dp" />
A 可见:  [A]--16dp--[B]
A gone:  [B]                 <- margin 变成 0

Chain(链)

1. 什么是 Chain

Chain 是 ConstraintLayout 中两个或多个 View 互相约束形成的一组关系。

2. 形成条件

关键:元素之间双向约束 + 首尾约束到 parent,形成闭环:

<A  app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toStartOf="@id/B" />     <!-- A 指向 B -->
 
<B  app:layout_constraintStart_toEndOf="@id/A"        <!-- B 指向 A(双向) -->
    app:layout_constraintEnd_toStartOf="@id/C" />
 
<C  app:layout_constraintStart_toEndOf="@id/B"
    app:layout_constraintEnd_toEndOf="parent" />
parent <-> [A] <-> [B] <-> [C] <-> parent
            ^  双向约束  ^

如果只有单向约束,就不是 chain

<!-- 这不是 chain,B 只是跟在 A 后面 -->
<TextView android:id="@+id/A"
    app:layout_constraintStart_toStartOf="parent" />
 
<TextView android:id="@+id/B"
    app:layout_constraintStart_toEndOf="@id/A" />
    <!-- 没有 End 约束,也没有 A 指向 B 的约束 -->
parent -> [A] [B]      <- 单向,不是 chain,自然左对齐

3. 三种 chainStyle

当 chain 形成后,系统要决定多余的空间怎么分配chainStyle 就是控制这个的,设在 chain 的第一个元素上:

spread(默认):空间均分
+----------------------------+
|   [A]     [B]     [C]     |
+----------------------------+

spread_inside:两端贴边,中间均分
+----------------------------+
|[A]        [B]        [C]  |
+----------------------------+

packed:全部挤在一起
+----------------------------+
|       [A][B][C]            |
+----------------------------+
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintHorizontal_chainStyle="packed"
<!-- 垂直方向同理 -->
app:layout_constraintVertical_chainStyle="packed"

4. packed + bias 实现左对齐

packed 模式下,元素挤在一起后,用 bias 控制这一坨放在哪:

<!-- 第一个元素上加这两行 -->
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintHorizontal_bias="0"
bias=0   -> 靠左    |[A][B][C]                |
bias=0.5 -> 居中    |       [A][B][C]         |
bias=1   -> 靠右    |                [A][B][C]|

5. 权重(配合 chain 使用)

类似 LinearLayout 的 weight,需要宽度设为 0dp

<A  android:layout_width="0dp"
    app:layout_constraintHorizontal_weight="1"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toStartOf="@id/B" />
<B  android:layout_width="0dp"
    app:layout_constraintHorizontal_weight="2"
    app:layout_constraintStart_toEndOf="@id/A"
    app:layout_constraintEnd_toStartOf="@id/C" />
<C  android:layout_width="0dp"
    app:layout_constraintHorizontal_weight="1"
    app:layout_constraintStart_toEndOf="@id/B"
    app:layout_constraintEnd_toEndOf="parent" />
+----------------------------+
| [ A  ] [    B    ] [ C  ] |    1:2:1 分配
+----------------------------+

6. 完整的 chain 示例

<!-- 三个按钮组成水平链 -->
<Button
    android:id="@+id/btn1"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toStartOf="@id/btn2"
    app:layout_constraintHorizontal_chainStyle="spread" />
 
<Button
    android:id="@+id/btn2"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toEndOf="@id/btn1"
    app:layout_constraintEnd_toStartOf="@id/btn3" />
 
<Button
    android:id="@+id/btn3"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toEndOf="@id/btn2"
    app:layout_constraintEnd_toEndOf="parent" />

圆形定位(Circular Positioning)

可以以另一个 View 为圆心,指定角度和半径来定位:

<Button android:id="@+id/center" ... />
 
<Button
    app:layout_constraintCircle="@id/center"
    app:layout_constraintCircleRadius="100dp"
    app:layout_constraintCircleAngle="45" />
<!-- 以 center 为圆心,半径 100dp,角度 45 度(12点方向为0度,顺时针) -->

辅助工具

1. Guideline(辅助线)

不可见的参考线,用于对齐。不会显示在界面上,只作为约束目标。

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    app:layout_constraintGuide_percent="0.3" />

定位方式三选一:

app:layout_constraintGuide_percent="0.3"   <!-- 百分比定位,在父容器30%处 -->
app:layout_constraintGuide_begin="100dp"   <!-- 距 start/top 的固定距离 -->
app:layout_constraintGuide_end="50dp"      <!-- 距 end/bottom 的固定距离 -->
+---------------------+
|                     |  30%
|- - - guideline - - -|
|                     |
|                     |  70%
+---------------------+

其他 View 约束到它:

<Button
    app:layout_constraintTop_toBottomOf="@id/guideline"
    app:layout_constraintStart_toStartOf="@id/guideline" />

2. Barrier(屏障)

根据多个 View 的实际边界动态生成的参考线。不管哪个 View 更大,barrier 都在最大的那个边界上:

<Button android:id="@+id/button1" ... />
<Button android:id="@+id/button2" ... />
 
<androidx.constraintlayout.widget.Barrier
    android:id="@+id/barrier"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:barrierDirection="end"
    app:constraint_referenced_ids="button1,button2" />
 
<TextView
    app:layout_constraintStart_toEndOf="@id/barrier" />
+-------------------------------+
| [名字]          |  [详情]     |
| [手机号码很长]   |  [详情]     |
|                barrier        |
+-------------------------------+

不管”名字”还是”手机号码”谁更长,barrier 都在最长的那个右边,后面的 View 约束到 barrier 就不会重叠。

barrierDirection 取值:

  • start — 取最左
  • end — 取最右
  • top — 取最上
  • bottom — 取最下

3. Group(组)

批量控制一组 View 的可见性:

<androidx.constraintlayout.widget.Group
    android:id="@+id/group"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="gone"
    app:constraint_referenced_ids="view1,view2,view3" />

代码中一行搞定:

group.visibility = View.GONE  // view1, view2, view3 全部隐藏

4. Flow(流式布局)

自动排列 + 换行,替代 FlexboxLayout:

<androidx.constraintlayout.helper.widget.Flow
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:flow_wrapMode="chain"
    app:flow_horizontalStyle="packed"
    app:flow_horizontalBias="0"
    app:flow_horizontalGap="8dp"
    app:flow_verticalGap="8dp"
    app:constraint_referenced_ids="tag1,tag2,tag3,tag4,tag5" />
+---------------------------+
| [tag1] [tag2] [tag3]      |   <- 放不下自动换行
| [tag4] [tag5]             |
+---------------------------+

wrapMode 取值:

模式说明
none不换行,超出也不管
chain换行,每行是一个 chain
aligned换行,网格对齐

5. Layer(层)

对多个 View 做整体变换(旋转、缩放、平移):

<androidx.constraintlayout.helper.widget.Layer
    android:id="@+id/layer"
    app:constraint_referenced_ids="view1,view2,view3" />
layer.rotation = 45f      // 整组旋转 45 度
layer.scaleX = 1.5f       // 整组放大 1.5 倍
layer.translationY = 20f  // 整组下移 20px

属性速查表

类别属性说明
位置constraintStart_toStartOf左对左
constraintStart_toEndOf左接右
constraintEnd_toStartOf右接左
constraintEnd_toEndOf右对右
constraintTop_toTopOf顶对顶
constraintTop_toBottomOf顶接底
constraintBottom_toTopOf底接顶
constraintBottom_toBottomOf底对底
constraintBaseline_toBaselineOf基线对齐
BiasconstraintHorizontal_bias水平偏移 0~1
constraintVertical_bias垂直偏移 0~1
尺寸layout_width="0dp"匹配约束
constraintWidth_percent百分比宽度
constraintHeight_percent百分比高度
constraintWidth_min/max最小/最大宽度
constraintHeight_min/max最小/最大高度
constraintDimensionRatio宽高比
constrainedWidth限制 wrap_content 不超出约束
constrainedHeight限制 wrap_content 不超出约束
ChainconstraintHorizontal_chainStyle链样式(spread/spread_inside/packed)
constraintVertical_chainStyle垂直链样式
constraintHorizontal_weight链内水平权重
constraintVertical_weight链内垂直权重
Gonelayout_goneMarginStart目标 gone 时的左 margin
layout_goneMarginEnd目标 gone 时的右 margin
layout_goneMarginTop目标 gone 时的上 margin
layout_goneMarginBottom目标 gone 时的下 margin
圆形constraintCircle圆形定位目标
constraintCircleRadius圆形半径
constraintCircleAngle圆形角度(0=12点,顺时针)
Guidelinelayout_constraintGuide_percent百分比定位
layout_constraintGuide_begin距 start/top 固定距离
layout_constraintGuide_end距 end/bottom 固定距离
BarrierbarrierDirection屏障方向(start/end/top/bottom)
constraint_referenced_ids引用的 View ID 列表

实战常见模式

1. 左图右文,文字占剩余空间

<ImageView android:id="@+id/icon"
    android:layout_width="48dp"
    android:layout_height="48dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
 
<TextView android:id="@+id/title"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toEndOf="@id/icon"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="@id/icon"
    android:layout_marginStart="12dp" />

2. 等分 3 列

<!-- 三个 View 形成 chain,宽度都为 0dp,自动均分 -->
<View android:id="@+id/c1" android:layout_width="0dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toStartOf="@id/c2" />
<View android:id="@+id/c2" android:layout_width="0dp"
    app:layout_constraintStart_toEndOf="@id/c1"
    app:layout_constraintEnd_toStartOf="@id/c3" />
<View android:id="@+id/c3" android:layout_width="0dp"
    app:layout_constraintStart_toEndOf="@id/c2"
    app:layout_constraintEnd_toEndOf="parent" />

3. 底部固定按钮 + 上方列表填满

<RecyclerView
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toTopOf="@id/btn"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent" />
 
<Button android:id="@+id/btn"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent" />

4. 适配不同屏幕(权重分配)

<Button android:id="@+id/btn1"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintHorizontal_weight="1"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toStartOf="@id/btn2" />
 
<Button android:id="@+id/btn2"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintHorizontal_weight="2"
    app:layout_constraintStart_toEndOf="@id/btn1"
    app:layout_constraintEnd_toEndOf="parent" />
<!-- btn2 占 btn1 两倍宽度 -->

5. 登录界面布局

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <!-- 标题 -->
    <TextView android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="64dp" />
 
    <!-- 用户名输入框 -->
    <EditText android:id="@+id/username"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_margin="24dp" />
 
    <!-- 密码输入框 -->
    <EditText android:id="@+id/password"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/username"
        app:layout_constraintStart_toStartOf="@id/username"
        app:layout_constraintEnd_toEndOf="@id/username" />
 
    <!-- 登录按钮 -->
    <Button android:id="@+id/login_button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/password"
        app:layout_constraintStart_toStartOf="@id/password"
        app:layout_constraintEnd_toEndOf="@id/password"
        android:layout_marginTop="32dp" />
 
    <!-- 底部链接 -->
    <TextView android:id="@+id/forgot_password"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginBottom="32dp" />
 
</androidx.constraintlayout.widget.ConstraintLayout>

性能最佳实践

  • 避免嵌套:所有视图直接放在 ConstraintLayout 中,不要再嵌套 LinearLayout 等
  • 合理使用 0dp:需要匹配约束时用 0dp,固定尺寸用具体数值
  • 不用 match_parent:用 0dp + 两侧约束代替
  • 使用 <include> 重用布局组件

常见问题解决

1. 视图重叠

  • 检查约束是否冲突
  • 确保使用了正确的约束关系
  • 使用 android:layout_margin 添加间距

2. 视图不可见

  • 检查约束是否完整(水平和垂直各至少一个)
  • 确认视图没有被其他视图覆盖
  • 检查 visibility 设置

3. 元素分散(想要左对齐)

  • 检查是否形成了 chain(双向约束),默认 chain 是 spread 模式
  • 方案一:chain 第一个元素加 chainStyle="packed" + bias="0"
  • 方案二:去掉双向约束,只保留单向的 Start_toEndOf,打破 chain

4. 布局错乱

  • 确保所有视图都有足够的约束
  • 避免混合使用 match_parent0dp
  • 检查边距设置

调试技巧

  • 在布局编辑器中点击 “Show Constraints” 按钮显示约束
  • 启用 “Autoconnect” 或 “Infer Constraints” 快速创建约束
  • 确保每个视图都有足够的约束(水平和垂直各至少一个)

相关笔记