Android compose中 ConstraintLayout 的使用

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

1. 依赖引入(必选)

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

arduino 复制代码
// 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. 最简示例(定位 + 间距)

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

scss 复制代码
@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 枚举:

scss 复制代码
@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()):

scss 复制代码
@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()

scss 复制代码
@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))
        }
    }
}

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

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

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

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

scss 复制代码
@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 无变更,用于创建虚拟参考线,适配多屏幕:

scss 复制代码
@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. 表单布局(多元素对齐)

scss 复制代码
@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. 卡片布局(多元素嵌套)

ini 复制代码
@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 函数,缩小重组范围:
kotlin 复制代码
// 推荐:抽离按钮组件
@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)
    }
}
  • 缓存不变的约束参数(如间距、尺寸):
scss 复制代码
@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. 避免无效约束

  • 不要同时设置 fillToConstraintsfillMaxWidth(冲突,仅保留一种);
  • 组件至少绑定 2 个约束(如 start + top),避免 "悬浮" 导致的重复测量;
  • 宽高比优先用 Modifier.aspectRatio(),而非手动计算尺寸。
相关推荐
阿巴斯甜5 小时前
Android LazyRow的使用
android jetpack
阿巴斯甜5 小时前
Android Row 的使用
android jetpack
林栩link5 小时前
Now in Android 现代应用开发实践(三):架构设计(UI)
android·android jetpack
段娇娇7 小时前
Android jetpack LiveData (二) 原理篇
android·android jetpack
阿巴斯甜9 小时前
Android LazyColumn的使用
android jetpack
阿巴斯甜1 天前
Compose 内置的 Modifier 用法总结
android jetpack
simplepeng1 天前
TikTok 通过 Jetpack Compose 将代码大小减少 58%,并提升了新功能的 app 性能
android·android jetpack
BoomHe1 天前
Kotlin shareIn 和 stateIn 使用场景
android·kotlin·android jetpack
俩个逗号。。2 天前
Compose 预览报错:java.lang.NoSuchMethodError
android·android jetpack