22、ConstraintLayout

46 阅读3分钟

Compose 中的 ConstraintLayout

ConstraintLayout 是一种布局,让您可以相对于屏幕上的其他可组合项来放置可组合项。它是使用多个嵌套的 RowColumnBox 等布局的实用替代方案,特别适合实现对齐要求复杂的大型布局。

何时使用 ConstraintLayout

  • 为了避免嵌套多个 ColumnRow,提高代码可读性
  • 需要相对于其他可组合项定位元素
  • 需要根据引导线、屏障线或链来定位可组合项

注意:在 Compose 中,深度嵌套的布局层次结构效率也很高,所以使用 ConstraintLayout 主要是为了代码可读性和复杂布局需求,而非性能优化。

开始使用

首先在 build.gradle 中添加依赖项:

implementation "androidx.constraintlayout:constraintlayout-compose:$constraintlayout_compose_version"

注意:constraintLayout-compose 工件的版本控制不同于 Jetpack Compose。查看 ConstraintLayout 版本页面获取最新版本。

基本用法

Compose 中的 ConstraintLayout 使用 DSL 操作:

  1. 使用 createRefs() 为每个可组合项创建引用
  2. 通过 constrainAs() 修饰符应用约束
  3. 使用 linkTo() 方法指定约束关系
  4. 使用 parent 引用来约束到父容器
@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // 创建可组合项的引用
        val (button, text) = createRefs()
        
        Button(
            onClick = { /* Do something */ },
            // 将引用"button"分配给Button可组合项
            // 并将其约束到ConstraintLayout的顶部
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }
        
        // 将引用"text"分配给Text可组合项
        // 并将其约束到Button底部
        Text(
            "Text",
            Modifier.constrainAs(text) {
                top.linkTo(button.bottom, margin = 16.dp)
            }
        )
    }
}

Decoupled API

有时需要将约束条件与布局分离,例如根据屏幕配置更改约束或在约束集之间添加动画:

@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // 纵向约束
        } else {
            decoupledConstraints(margin = 32.dp) // 横向约束
        }
        
        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button")
            ) {
                Text("Button")
            }
            Text("Text", 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)
        }
    }
}

ConstraintLayout 概念

引导线

引导线是用于定位元素的视觉辅助工具,有两种类型:垂直和水平。

ConstraintLayout {
    // 在父容器宽度10%处创建从起始位置开始的引导线
    val startGuideline = createGuidelineFromStart(0.1f)
    // 在父容器宽度10%处创建从结束位置开始的引导线
    val endGuideline = createGuidelineFromEnd(0.1f)
    // 在距离父容器顶部16dp处创建引导线
    val topGuideline = createGuidelineFromTop(16.dp)
    // 在距离父容器底部16dp处创建引导线
    val bottomGuideline = createGuidelineFromBottom(16.dp)
}

屏障线

屏障线引用多个可组合项,根据指定边中最边缘的 widget 创建虚拟引导线。

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")
        val topBarrier = createTopBarrier(button, text)
    }
}

链在单条轴(水平或垂直)上提供类似组的行为。

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")
        val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
        val horizontalChain = createHorizontalChain(button, text)
    }
}

链可通过不同的 ChainStyle 配置:

  • ChainStyle.Spread:空间均匀分布在所有可组合项之间,包括首尾元素外的空间
  • ChainStyle.SpreadInside:空间均匀分布在所有可组合项之间,不包括首尾元素外的空间
  • ChainStyle.Packed:空间分布在首尾元素之外,各元素挤在一起

如需更多示例,可查看 Compose 示例 中的实际应用。