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"

Compose 中的 ConstraintLayout 使用 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)
            }
        )
    }
}

此代码使用 16.dp 的外边距来约束 Button 顶部到父项的距离,同样使用 16.dp 的外边距来约束 TextButton 底部的距离。

显示了按 ConstraintLayout 排列的按钮和文本元素

Decoupled 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 包含引导线、屏障线和链等概念,掌握这些概念有助于在您的可组合项内定位元素。

引导线

引导线是设计布局时使用的小型视觉辅助工具。可组合项可以受引导线约束。在父级可组合项内的特定 dppercentage 处定位元素时,引导线很有用。

有两种不同的引导线:垂直和水平引导线。两种水平引导线分别是 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() 块中使用的引用。

屏障线

屏障线会引用多个可组合项,从而根据所指定边中处于最边缘位置的 widget 创建虚拟引导线。

若要创建屏障线,请使用 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:空间会分布在第一个可组合项之前和最后一个可组合项之后,各个可组合项之间没有空间,会挤在一起。

了解详情

如需详细了解 Compose 中的 ConstraintLayout,请参阅使用 ConstraintLayout 的 Compose 示例中的 API 的实际运用。