Android compose中 ConstraintLayout 的使用

11 阅读5分钟

一、核心准备(依赖 + 基础概念)

1. 依赖引入(必选)

在 Module 级 build.gradle.kts/build.gradle 中添加:

// build.gradle.kts (Kotlin DSL)
    dependencies {
        implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1")
    }

// 或 build.gradle (Groovy)
    dependencies {
        implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
    }

2. 核心概念(1.0.1+ 版本统一)

概念作用
createRefs()批量创建组件引用(替代单个 createRef(),推荐解构赋值)
constrainAs()唯一约束入口:所有组件的位置 / 尺寸约束都通过 Modifier.constrainAs(ref) 定义
约束方向start/end/top/bottom/baseline/center 等,统一通过 linkTo() 绑定
尺寸约束移至 Modifier(如 aspectRatio()),不再在约束闭包内定义

二、基础用法(入门必掌握)

1. 最简示例(定位 + 间距)

实现 “标题左对齐、按钮右对齐、底部文本居中” 的经典布局,无任何嵌套:

@Composable
fun BasicConstraintLayout() {
    ConstraintLayout(
        modifier = Modifier
            .fillMaxWidth()
            .height(120.dp)
            .padding(16.dp)
            .background(Color.LightGray.copy(alpha = 0.1f))
    ) {
        // 1. 创建组件引用(解构赋值)
        val (titleRef, btnRef, descRef) = createRefs()

        // 2. 标题:左对齐父布局,顶部间距8dp
        Text(
            text = "订单详情",
            fontSize = 18.sp,
            modifier = Modifier.constrainAs(titleRef) { //.padding(start = 8.dp,top = 8.dp)
                start.linkTo(parent.start, margin = 8.dp) // 约束+间距(1.0.1+ 推荐写法)
                top.linkTo(parent.top, margin = 8.dp)
            }
        )

        // 3. 按钮:右对齐父布局,与标题顶部平齐
        Button(
            onClick = {},
            modifier = Modifier.constrainAs(btnRef) {
                end.linkTo(parent.end, margin = 8.dp)
                top.linkTo(titleRef.top) // 绑定到标题的顶部
            }
        ) {
            Text("编辑")
        }

        // 4. 描述文本:水平居中,在标题下方
        Text(
            text = "创建时间:2026-03-18",
            fontSize = 12.sp,
            color = Color.Gray,
            modifier = Modifier.constrainAs(descRef) {
                centerHorizontallyTo(parent) // 水平居中父布局
                top.linkTo(titleRef.bottom, margin = 16.dp)
            }
        )
    }
}

核心要点

  • 所有约束必须写在 constrainAs(ref) 闭包内;
  • 间距通过 linkTo(target, margin = x.dp) 绑定(1.0.1+ 移除了 marginStart/marginTop 直接赋值);
  • 组件至少需要 2 个约束(如 start + top),否则会测量异常。
  • Modifier.padding(start = 8.dp,top = 8.dp)linkTo(target, margin = x.dp)效果是一样的;

2. 尺寸约束(1.0.1+ 统一规则)

组件宽高优先通过 Modifier 定义,或在约束闭包内用 Dimension 枚举:

@Composable
fun SizeConstraintDemo() {
    ConstraintLayout(Modifier.fillMaxWidth().padding(16.dp)) {
        val (boxRef) = createRefs()

        Box(
            modifier = Modifier
                .constrainAs(boxRef) {
                    start.linkTo(parent.start)
                    top.linkTo(parent.top)
                    // 约束闭包内定义尺寸(1.0.1+ 兼容)
                    width = Dimension.value(100.dp) // 固定宽度
                    height = Dimension.wrapContent // 自适应高度
                    // 填充约束空间(替代 fillMaxWidth)
                    // width = Dimension.fillToConstraints
                }
                .background(Color.Blue)
        )
    }
}
Dimension 枚举作用
Dimension.value(x.dp)固定尺寸(推荐优先用 Modifier.size()
Dimension.wrapContent自适应内容(等价于 Modifier.wrapContentSize()
Dimension.fillToConstraints填充约束范围内的空间(如 start+end 约束之间)

三、核心约束规则(1.0.1+ 重点)

1. 基础约束 API(全量)

约束类型API 示例说明
父布局约束centerHorizontallyTo(parent)水平居中父布局
centerVerticallyTo(parent)垂直居中父布局
centerTo(parent)水平 + 垂直居中
组件间约束baseline.linkTo(titleRef.baseline)文字基线对齐(仅文本类组件)
双向约束top.linkTo(parent.top) + bottom.linkTo(parent.bottom)垂直居中(无 margin 时)
间距约束start.linkTo(btnRef.end, margin = 12.dp)约束 + 间距(1.0.1+ 唯一写法)

2. 链约束(Chain):替代 Row/Column

1.0.1+ 保留链功能,但需将链内组件的对齐约束写在 constrainAs 内(移除了 constrain()):

@Composable
fun ChainConstraintDemo() {
    ConstraintLayout(Modifier.fillMaxWidth().height(80.dp).padding(16.dp)) {
        val (btn1Ref, btn2Ref, btn3Ref) = createRefs()

        // 1. 创建水平链(1.0.1+ 兼容)
        createHorizontalChain(
            btn1Ref, btn2Ref, btn3Ref,
            chainStyle = ChainStyle.Spread, // 均匀分布(可选:SpreadInside/Packed)
            //spacing = 12.dp // 链内组件间距(1.0.1+ 新增便捷参数)
        )

        // 2. 按钮1:链内约束 + 垂直居中
        Button(
            onClick = {},
            modifier = Modifier.constrainAs(btn1Ref) {
                top.linkTo(parent.top, margin = 4.dp)
                bottom.linkTo(parent.bottom, margin = 4.dp)
            }
        ) { Text("按钮1") }

        // 3. 按钮2/3 同按钮1(省略)
        Button(
            onClick = {},
            modifier = Modifier.constrainAs(btn2Ref) {
                top.linkTo(parent.top, margin = 4.dp)
                bottom.linkTo(parent.bottom, margin = 4.dp)
            }
        ) { Text("按钮2") }

        Button(
            onClick = {},
            modifier = Modifier.constrainAs(btn3Ref) {
                top.linkTo(parent.top, margin = 4.dp)
                bottom.linkTo(parent.bottom, margin = 4.dp)
            }
        ) { Text("按钮3") }
    }
}

链样式说明

  • ChainStyle.Spread:组件均匀分布(默认);
  • ChainStyle.SpreadInside:首尾贴边,中间均匀;
  • ChainStyle.Packed:组件紧凑排列(可通过 margin 调整间距)。

3. 宽高比约束(1.0.1+ 核心变更)

1.0.1+ 移除了约束闭包内的 aspectRatio 属性,统一改为 Modifier.aspectRatio()

@Composable
fun AspectRatioConstraintDemo() {
    ConstraintLayout(Modifier.fillMaxWidth().padding(16.dp)) {
        val (imageRef) = createRefs()

        Box(
            modifier = Modifier
                .aspectRatio(16f / 9f) // 16:9 宽高比(1.0.1+ 唯一写法)
                .constrainAs(imageRef) {
                    start.linkTo(parent.start)
                    end.linkTo(parent.end)
                    top.linkTo(parent.top)
                }
                .background(Color.Gray)
        ) {
            Text("16:9 图片占位", modifier = Modifier.align(Alignment.Center))
        }
    }
}

进阶用法(固定高度 + 比例):

// 固定高度180dp,宽度按16:9适配
Modifier
.height(180.dp)
.aspectRatio(16f / 9f, matchHeightConstraintsFirst = true) // 优先匹配高度

4. 屏障(Barrier):多组件边缘对齐

1.0.1+ 保留屏障功能,用于创建 “虚拟边缘”,适配动态长度的组件:

@Composable
fun BarrierConstraintDemo() {
    ConstraintLayout(Modifier.fillMaxWidth().padding(16.dp).height(100.dp)) {
        val (txt1Ref, txt2Ref, btnRef) = createRefs()

        // 动态长度的文本
        Text(
            text = "短文本",
            modifier = Modifier.constrainAs(txt1Ref) {
                start.linkTo(parent.start)
                top.linkTo(parent.top)
            }
        )
        Text(
            text = "很长很长的文本,换行显示",
            modifier = Modifier.constrainAs(txt2Ref) {
                start.linkTo(txt1Ref.start)
                top.linkTo(txt1Ref.bottom, margin = 4.dp)
            }
        )

        // 1. 创建屏障:在两个文本的右侧
        val barrier = createEndBarrier(txt1Ref, txt2Ref)

        // 2. 按钮绑定到屏障右侧
        Button(
            onClick = {},
            modifier = Modifier.constrainAs(btnRef) {
                start.linkTo(barrier, margin = 8.dp)
                top.linkTo(parent.top)
            }
        ) {
            Text("按钮")
        }
    }
}

5. 指导线(Guideline):百分比定位

1.0.1+ 指导线 API 无变更,用于创建虚拟参考线,适配多屏幕:

@Composable
fun GuidelineConstraintDemo() {
    ConstraintLayout(Modifier.fillMaxSize().padding(16.dp)) {
        val (btnRef) = createRefs()

        // 1. 创建指导线:左侧1/3位置 + 顶部50dp
        val verticalGuideline = createGuidelineFromStart(fraction = 1f / 3f)
        val horizontalGuideline = createGuidelineFromTop(50.dp)

        // 2. 按钮绑定到指导线
        Button(
            onClick = {},
            modifier = Modifier.constrainAs(btnRef) {
                start.linkTo(verticalGuideline)
                top.linkTo(horizontalGuideline)
            }
        ) {
            Text("指导线对齐")
        }
    }
}

四、高级实战场景(1.0.1+ 适配)

1. 表单布局(多元素对齐)


@Composable
fun FormLayoutDemo() {
    ConstraintLayout(Modifier.fillMaxWidth().padding(16.dp)) {
        val (label1Ref, input1Ref, label2Ref, input2Ref, submitRef) = createRefs()

        // 标签1:左对齐,顶部间距
        Text(
            text = "姓名:",
            modifier = Modifier.constrainAs(label1Ref) {
                start.linkTo(parent.start)
                top.linkTo(parent.top, margin = 8.dp)
                width = Dimension.value(80.dp) // 固定宽度,对齐所有标签
            }
        )
        // 输入框1:标签右侧,占满剩余宽度
        Box(
            modifier = Modifier
                .height(40.dp)
                .background(Color.LightGray)
                .constrainAs(input1Ref) {
                    start.linkTo(label1Ref.end, margin = 8.dp)
                    end.linkTo(parent.end)
                    top.linkTo(label1Ref.top)
                }
        ) { Text("输入框", modifier = Modifier.align(Alignment.CenterStart).padding(8.dp)) }

        // 标签2:与标签1左对齐,在输入框1下方
        Text(
            text = "手机号:",
            modifier = Modifier.constrainAs(label2Ref) {
                start.linkTo(label1Ref.start)
                top.linkTo(input1Ref.bottom, margin = 16.dp)
            }
        )
        // 输入框2:与输入框1左对齐
        Box(
            modifier = Modifier
                .height(40.dp)
                .background(Color.LightGray)
                .constrainAs(input2Ref) {
                    start.linkTo(input1Ref.start)
                    end.linkTo(input1Ref.end)
                    top.linkTo(label2Ref.top)
                }
        ) { Text("输入框", modifier = Modifier.align(Alignment.CenterStart).padding(8.dp)) }

        // 提交按钮:水平居中,在输入框2下方
        Button(
            onClick = {},
            modifier = Modifier.constrainAs(submitRef) {
                centerHorizontallyTo(parent)
                top.linkTo(input2Ref.bottom, margin = 24.dp)
            }
        ) {
            Text("提交")
        }
    }
}

2. 卡片布局(多元素嵌套)

@Composable
fun CardLayoutDemo() {
    ConstraintLayout(
        modifier = Modifier
            .fillMaxWidth()
            .height(200.dp)
            .padding(16.dp)
            .background(Color.White)
            .shadow(4.dp)
    ) {
        val (avatarRef, nameRef, descRef, tagRef, arrowRef) = createRefs()

        // 头像:左上角,固定尺寸
        Box(
            modifier = Modifier
                .size(48.dp)
                .background(Color.Blue)
                .constrainAs(avatarRef) {
                    start.linkTo(parent.start, margin = 16.dp)
                    top.linkTo(parent.top, margin = 16.dp)
                }
        )

        // 姓名:头像右侧,顶部对齐
        Text(
            text = "张三",
            fontSize = 16.sp,
            fontWeight = FontWeight.Bold,
            modifier = Modifier.constrainAs(nameRef) {
                start.linkTo(avatarRef.end, margin = 12.dp)
                top.linkTo(avatarRef.top)
            }
        )

        // 描述:姓名下方
        Text(
            text = "Android 开发工程师",
            fontSize = 12.sp,
            color = Color.Gray,
            modifier = Modifier.constrainAs(descRef) {
                start.linkTo(nameRef.start)
                top.linkTo(nameRef.bottom, margin = 4.dp)
            }
        )

        // 标签:右下角
        Box(
            modifier = Modifier
                .padding(horizontal = 8.dp, vertical = 4.dp)
                .background(Color.Green.copy(alpha = 0.2f))
                .constrainAs(tagRef) {
                    end.linkTo(parent.end, margin = 16.dp)
                    bottom.linkTo(parent.bottom, margin = 16.dp)
                }
        ) {
            Text("认证用户", fontSize = 12.sp, color = Color.Green)
        }

        // 箭头:右上角
        Box(
            modifier = Modifier
                .size(24.dp)
                .constrainAs(arrowRef) {
                    end.linkTo(parent.end, margin = 16.dp)
                    top.linkTo(parent.top, margin = 16.dp)
                }
        ) {
            Text(">", modifier = Modifier.align(Alignment.Center))
        }
    }
}

五、性能优化(1.0.1+ 避坑关键)

1. 减少约束复杂度

  • 简单线性布局(如纯横向 / 纵向)优先用 Row/Column,仅复杂布局用 ConstraintLayout;
  • 避免嵌套 ConstraintLayout(1.0.1+ 嵌套会增加 2 倍以上测量耗时);
  • 减少不必要的屏障 / 链(每增加一个屏障,测量步骤 + 1)。

2. 减少重组

  • 抽离子组件为独立 Composable 函数,缩小重组范围:
// 推荐:抽离按钮组件
@Composable
private fun ConstraintButton(ref: Ref, text: String, onClick: () -> Unit) {
    Button(
        onClick = onClick,
        modifier = Modifier.constrainAs(ref) {
            top.linkTo(parent.top, margin = 4.dp)
            bottom.linkTo(parent.bottom, margin = 4.dp)
        }
    ) {
        Text(text)
    }
}
  • 缓存不变的约束参数(如间距、尺寸):
@Composable
fun OptimizedLayout() {
    val margin = remember { 8.dp } // 缓存间距,避免重组时重新创建
    ConstraintLayout(Modifier.fillMaxWidth()) {
        val (ref) = createRefs()
        Text(
            text = "优化重组",
            modifier = Modifier.constrainAs(ref) {
                start.linkTo(parent.start, margin = margin)
            }
        )
    }
}

3. 避免无效约束

  • 不要同时设置 fillToConstraints 和 fillMaxWidth(冲突,仅保留一种);
  • 组件至少绑定 2 个约束(如 start + top),避免 “悬浮” 导致的重复测量;
  • 宽高比优先用 Modifier.aspectRatio(),而非手动计算尺寸。