Skip to content

约束布局 (ConstraintLayout)

源:Compose 中的 ConstraintLayout

虽然 ColumnRowBox 足以应付大多数场景,但在处理复杂的相对定位关系时,嵌套层级可能会变得很深。ConstraintLayout 可以让你构建扁平化的复杂布局。

添加依赖

Compose 的 ConstraintLayout 是一个独立的库:

kotlin
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

基本用法

  1. 使用 createRefs() 创建引用。
  2. 使用 Modifier.constrainAs(ref) 将组件绑定到引用。
  3. constrainAs lambda 中定义约束条件(linkTo)。
kotlin
@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout(modifier = Modifier.fillMaxSize()) {
        // 1. 创建引用
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // 2. 绑定引用并添加约束
            modifier = Modifier.constrainAs(button) {
                // 顶部距离父容器顶部 16dp
                top.linkTo(parent.top, margin = 16.dp)
                // 居中
                start.linkTo(parent.start)
                end.linkTo(parent.end)
            }
        ) {
            Text("Button")
        }

        Text(
            text = "Text",
            modifier = Modifier.constrainAs(text) {
                // 顶部位于 button 的底部,间距 16dp
                top.linkTo(button.bottom, margin = 16.dp)
                // 水平居中于 button
                centerHorizontallyTo(button)
            }
        )
    }
}

屏障 (Barrier)

参考线会根据多个元素中最宽/最高的那个来定位。

kotlin
val barrier = createEndBarrier(button1, button2)
Text(
    modifier = Modifier.constrainAs(text) {
        start.linkTo(barrier)
    }
)

引导线 (Guideline)

不可见的参考线。

kotlin
val guideline = createGuidelineFromTop(0.5f) // 位于顶部 50% 处

链 (Chain)

将多个组件按特定方式排列(类似 Row/Column 的 Arrangement)。

kotlin
createHorizontalChain(button1, button2, chainStyle = ChainStyle.Spread)

解耦约束 (Decoupled API)

可以将约束逻辑与布局声明分离,这样可以根据屏幕配置(如横屏/竖屏)动态改变约束集 ConstraintSet,而无需重写 UI 结构。

kotlin
@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (maxWidth < maxHeight) {
            decoupledConstraints(margin = 16.dp) // 竖屏约束
        } else {
            decoupledConstraints(margin = 32.dp) // 横屏约束
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button") // 使用 ID 关联
            ) {
                Text("Button")
            }

            Text(
                text = "Text",
                modifier = Modifier.layoutId("text")
            )
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin = margin)
        }
    }
}