一、核心准备(依赖 + 基础概念)
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(),而非手动计算尺寸。