ConstraintLayout in Compose

ConstraintLayout can help place composables relative to others on the screen, and is an alternative to using multiple nested Row, Column, Box and custom layout elements. ConstraintLayout is useful when implementing larger layouts with more complicated alignment requirements.

To use ConstraintLayout in Compose, you need to add this dependency in your build.gradle:

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-beta02"

ConstraintLayout in Compose works with a DSL:

  • References are created using createRefs() or createRefFor(), and each composable in ConstraintLayoutneeds to have a reference associated with it.
  • Constraints are provided using the constrainAs() modifier, which takes the reference as a parameter and lets you specify its constraints in the body lambda.
  • Constraints are specified using linkTo() or other helpful methods.
  • parent is an existing reference that can be used to specify constraints towards the ConstraintLayout composable itself.

Here's an example of a composable using a 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)
        })
    }
}

This code constrains the top of the Button to the parent with a margin of 16.dp and a Text to the bottom of the Button also with a margin of 16.dp.

Shows a button and a text element arranged in a ConstraintLayout

For more examples of how to work with ConstraintLayout, try the layouts codelab.

Decoupled API

In the ConstraintLayout example, constraints are specified inline, with a modifier in the composable they're applied to. However, there are situations when it's preferable to decouple the constraints from the layouts they apply to. For example, you might want to change the constraints based on the screen configuration, or animate between two constraint sets.

For cases like these, you can use ConstraintLayout in a different way:

  1. Pass in a ConstraintSet as a parameter to ConstraintLayout.
  2. Assign references created in the ConstraintSet to composables using the layoutId modifier.
@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)
        }
    }
}

Then, when you need to change the constraints, you can just pass a different ConstraintSet.

Learn more

Learn more about ConstraintLayout in Compose in the Constraint Layout section of the Layouts in Jetpack Compose codelab, and see the APIs in action in the Compose samples that use ConstraintLayout.