Compose 中的 ConstraintLayout

ConstraintLayout 是一种布局,允许您将可组合项相对于屏幕上的其他可组合项进行放置。它是使用多个嵌套的 RowColumnBox其他自定义布局元素 的替代方案。当实现具有更复杂对齐要求的更大布局时,ConstraintLayout 非常有用。

考虑在以下情况下使用 ConstraintLayout

  • 为了避免嵌套多个 ColumnRow 来定位屏幕上的元素,从而提高代码的可读性。
  • 将可组合项相对于其他可组合项进行定位,或根据指南、屏障或链定位可组合项。

在 View 系统中,ConstraintLayout 是创建大型复杂布局的推荐方法,因为扁平的视图层次结构比嵌套的视图更利于性能。但是,这在 Compose 中不是问题,Compose 可以有效地处理深层布局层次结构。

开始使用 ConstraintLayout

要在 Compose 中使用 ConstraintLayout,您需要在您的 build.gradle 中添加此依赖项(除了 Compose 设置 之外)

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

ConstraintLayout 在 Compose 中的工作方式如下,使用 DSL

  • 使用 createRefs()createRefFor()ConstraintLayout 中为每个可组合项创建引用。
  • 使用 constrainAs() 修饰符提供约束,该修饰符以引用作为参数,并允许您在主体 lambda 中指定其约束。
  • 使用 linkTo() 或其他有用的方法指定约束。
  • parent 是一个现有引用,可用于指定针对 ConstraintLayout 可组合项本身的约束。

以下是用 ConstraintLayout 的可组合项示例

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // Create references for the composables to constrain
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // Assign reference "button" to the Button composable
            // and constrain it to the top of the ConstraintLayout
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        // Assign reference "text" to the Text composable
        // and constrain it to the bottom of the Button composable
        Text(
            "Text",
            Modifier.constrainAs(text) {
                top.linkTo(button.bottom, margin = 16.dp)
            }
        )
    }
}

此代码将 Button 的顶部约束到父元素,边距为 16.dp,并将 Text 约束到 Button 的底部,边距也为 16.dp

Shows a button and a text element arranged in a ConstraintLayout

解耦的 API

ConstraintLayout 示例中,约束是内联指定的,在它们应用到的可组合项中使用修饰符。但是,在某些情况下,最好将约束与其应用到的布局解耦。例如,您可能希望根据屏幕配置更改约束,或在两个约束集之间进行动画。

对于此类情况,您可以以不同的方式使用 ConstraintLayout

  1. ConstraintSet 作为参数传递给 ConstraintLayout
  2. 使用 layoutId 修饰符将 ConstraintSet 中创建的引用分配给可组合项。

@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        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)
        }
    }
}

然后,当您需要更改约束时,您只需传递一个不同的 ConstraintSet 即可。

ConstraintLayout 概念

ConstraintLayout 包含指南、屏障和链等概念,这些概念可以帮助您在可组合项内部定位元素。

指南

指南是用于设计布局的小型视觉辅助工具。可组合项可以约束到指南。指南对于将元素定位在父级可组合项内的特定 dp百分比 位置很有用。

有两种不同的 指南,垂直和水平。两个水平方向的指南是 topbottom,两个垂直方向的指南是 startend

ConstraintLayout {
    // Create guideline from the start of the parent at 10% the width of the Composable
    val startGuideline = createGuidelineFromStart(0.1f)
    // Create guideline from the end of the parent at 10% the width of the Composable
    val endGuideline = createGuidelineFromEnd(0.1f)
    //  Create guideline from 16 dp from the top of the parent
    val topGuideline = createGuidelineFromTop(16.dp)
    //  Create guideline from 16 dp from the bottom of the parent
    val bottomGuideline = createGuidelineFromBottom(16.dp)
}

要创建指南,请使用 createGuidelineFrom* 以及所需的指南类型。这将创建一个可以在 Modifier.constrainAs() 块中使用的引用。

屏障

屏障 参考多个可组合项,以基于指定侧面上最极端的窗口小部件创建虚拟指南。

要创建屏障,请使用 createTopBarrier()(或:createBottomBarrier()createEndBarrier()createStartBarrier()),并提供应构成屏障的引用。

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val topBarrier = createTopBarrier(button, text)
    }
}

然后可以在 Modifier.constrainAs() 块中使用屏障。

链在单个轴(水平或垂直)上提供类似于组的行为。另一个轴可以独立约束。

要创建链,请使用 createVerticalChaincreateHorizontalChain

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)
    }
}

然后可以在 Modifier.constrainAs() 块中使用链。

链可以使用不同的 ChainStyles 进行配置,这些样式决定了如何处理围绕可组合项的空间,例如

  • ChainStyle.Spread:空间均匀分布在所有可组合项上,包括第一个可组合项之前的自由空间和最后一个可组合项之后的自由空间。
  • ChainStyle.SpreadInside:空间均匀分布在所有可组合项上,第一个可组合项之前或最后一个可组合项之后没有自由空间。
  • ChainStyle.Packed:空间分布在第一个之前和最后一个之后,可组合项彼此紧密排列,彼此之间没有间隙。

了解更多

从使用 ConstraintLayoutCompose 示例 中的操作 API 中了解有关 Compose 中 ConstraintLayout 的更多信息。