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. 避免无效约束

  • 不要同时设置 fillToConstraints 和 fillMaxWidth(冲突,仅保留一种);
  • 组件至少绑定 2 个约束(如 start + top),避免 "悬浮" 导致的重复测量;
  • 宽高比优先用 Modifier.aspectRatio(),而非手动计算尺寸。
相关推荐
alexhilton3 天前
模块化Jetpack Compose架构
android·kotlin·android jetpack
hai_android3 天前
InputStage 责任链核心执行流程
android·android studio·android jetpack
传奇开心果编程4 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程5 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程5 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
天空之城--7 天前
Android一周动态:Android 18首次官宣、Compose Material3 1.4转正(5趋势+5资讯)
android·人工智能·flutter·架构·android jetpack
传奇开心果编程8 天前
【现代声明式UI学与练】第1课 从命令式UI到声明式UI
学习·flutter·ui·swiftui·react·android jetpack
传奇开心果编程11 天前
【用案例学Material 3 Expressive】第1课:从一封邮件开始,感受安卓设计新语言的表现力
android·学习·ui·kotlin·android jetpack
传奇开心果编程12 天前
【Jetpack Compose进阶学与练】第14课:系列收尾复习总结;Compose项目常见坑点汇总;学习路线与后续学习方向
android·学习·ui·kotlin·android jetpack
hai_android12 天前
子 View 被压小时,如何向父容器"告状"?——MEASURED_STATE_TOO_SMALL 向上传递机制
android·kotlin·android jetpack