ConstraintLayout

Functions summary

inline Unit
@Composable
ConstraintLayout(
    modifier: Modifier,
    optimizationLevel: Int,
    animateChangesSpec: AnimationSpec<Float>?,
    noinline finishedAnimationListener: (() -> Unit)?,
    crossinline content: @Composable ConstraintLayoutScope.() -> Unit
)

Layout that positions its children according to the constraints between them.

inline Unit
@Composable
ConstraintLayout(
    constraintSet: ConstraintSet,
    modifier: Modifier,
    optimizationLevel: Int,
    animateChangesSpec: AnimationSpec<Float>?,
    noinline finishedAnimationListener: (() -> Unit)?,
    crossinline content: @Composable () -> Unit
)

Layout that positions its children according to the constraints between them.

inline Unit
@Composable
ConstraintLayout(
    modifier: Modifier,
    optimizationLevel: Int,
    animateChanges: Boolean,
    animationSpec: AnimationSpec<Float>,
    noinline finishedAnimationListener: (() -> Unit)?,
    crossinline content: @Composable ConstraintLayoutScope.() -> Unit
)

This function is deprecated. Prefer version that takes a nullable AnimationSpec to animate changes.

inline Unit
@Composable
ConstraintLayout(
    constraintSet: ConstraintSet,
    modifier: Modifier,
    optimizationLevel: Int,
    animateChanges: Boolean,
    animationSpec: AnimationSpec<Float>,
    noinline finishedAnimationListener: (() -> Unit)?,
    crossinline content: @Composable () -> Unit
)

This function is deprecated. Prefer version that takes a nullable AnimationSpec to animate changes.

Functions

@Composable
inline fun ConstraintLayout(
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    animateChangesSpec: AnimationSpec<Float>? = null,
    noinline finishedAnimationListener: (() -> Unit)? = null,
    crossinline content: @Composable ConstraintLayoutScope.() -> Unit
): Unit

Layout that positions its children according to the constraints between them.

Constraints are defined within the content of this ConstraintLayout Composable.

Items in the layout that are to be constrained are initialized with ConstraintLayoutScope.createRef:

val textRef = createRef()
val imageRef = createRef()

You may also use ConstraintLayoutScope.createRefs to declare up to 16 items using the destructuring declaration pattern:

val (textRef, imageRef) = createRefs()

Individual constraints are defined with Modifier.constrainAs, this will also bind the Composable to the given ConstrainedLayoutReference.

So, a simple layout with a text in the middle and an image next to it may be declared like this (keep in mind, when using center..., start or end the layout direction will automatically change in RTL locales):

ConstraintLayout(Modifier.fillMaxSize()) {
val (textRef, imageRef) = createRefs()
Text(
modifier = Modifier.constrainAs(textRef) {
centerTo(parent)
},
text = "Hello, World!"
)
Image(
modifier = Modifier.constrainAs(imageRef) {
centerVerticallyTo(textRef)
start.linkTo(textRef.end, margin = 8.dp)
},
imageVector = Icons.Default.Android,
contentDescription = null
)
}

See ConstrainScope to learn more about how to constrain elements together.

Helpers

You may also use helpers, a set of virtual (not shown on screen) components that provide special layout behaviors, you may find these in the ConstraintLayoutScope with the 'create...' prefix, a few of these are Guidelines, Chains and Barriers.

Guidelines

Lines to which other ConstrainedLayoutReferences may be constrained to, these are defined at either a fixed or percent position from an anchor of the ConstraintLayout parent (top, bottom, start, end, absoluteLeft, absoluteRight).

Example:

val (textRef) = createRefs()
val vG = createGuidelineFromStart(fraction = 0.3f)
Text(
modifier = Modifier.constrainAs(textRef) {
centerVerticallyTo(parent)
centerAround(vG)
},
text = "Hello, World!"
)

See

Chains

Chains may be either horizontal or vertical, these, take a set of ConstrainedLayoutReferences and create bi-directional constraints on each of them at the same orientation of the chain in the given order, meaning that an horizontal chain will create constraints between the start and end anchors.

The result, a layout that evenly distributes the space within its elements.

For example, to make a layout with three text elements distributed so that the spacing between them (and around them) is equal:

val (textRef0, textRef1, textRef2) = createRefs()
createHorizontalChain(textRef0, textRef1, textRef2, chainStyle = ChainStyle.Spread)

Text(modifier = Modifier.constrainAs(textRef0) {}, text = "Hello")
Text(modifier = Modifier.constrainAs(textRef1) {}, text = "Foo")
Text(modifier = Modifier.constrainAs(textRef2) {}, text = "Bar")

You may set margins within elements in a chain with ConstraintLayoutScope.withChainParams:

val (textRef0, textRef1, textRef2) = createRefs()
createHorizontalChain(
textRef0,
textRef1.withChainParams(startMargin = 100.dp, endMargin = 100.dp),
textRef2,
chainStyle = ChainStyle.Spread
)

Text(modifier = Modifier.constrainAs(textRef0) {}, text = "Hello")
Text(modifier = Modifier.constrainAs(textRef1) {}, text = "Foo")
Text(modifier = Modifier.constrainAs(textRef2) {}, text = "Bar")

You can also change the way space is distributed, as chains have three different styles:

  • ChainStyle.Spread Layouts are evenly distributed after margins are accounted for (the space around and between each item is even). This is the default style for chains.

  • ChainStyle.SpreadInside The first and last layouts are affixed to each end of the chain, and the rest of the items are evenly distributed (after margins are accounted for). I.e.: Items are spread from the inside, distributing the space between them with no space around the first and last items.

  • ChainStyle.Packed The layouts are packed together after margins are accounted for, by default, they're packed together at the middle, you can change this behavior with the bias parameter of ChainStyle.Packed.

  • Alternatively, you can make every Layout in the chain to be Dimension.fillToConstraints and then set a particular weight to each of them to create a weighted chain.

Weighted Chain

Weighted chains are useful when you want the size of the elements to depend on the remaining size of the chain. As opposed to just distributing the space around and/or in-between the items.

For example, to create a layout with three text elements in a row where each element takes the exact same size regardless of content, you can use a simple weighted chain where each item has the same weight:

val (textRef0, textRef1, textRef2) = createRefs()
createHorizontalChain(
textRef0.withChainParams(weight = 1f),
textRef1.withChainParams(weight = 1f),
textRef2.withChainParams(weight = 1f),
chainStyle = ChainStyle.Spread
)

Text(modifier = Modifier.background(Color.Cyan).constrainAs(textRef0) {
width = Dimension.fillToConstraints
}, text = "Hello, World!")
Text(modifier = Modifier.background(Color.Red).constrainAs(textRef1) {
width = Dimension.fillToConstraints
}, text = "Foo")
Text(modifier = Modifier.background(Color.Cyan).constrainAs(textRef2) {
width = Dimension.fillToConstraints
}, text = "This text is six words long")

This way, the texts will horizontally occupy the same space even if one of them is significantly larger than the others.

Keep in mind that chains have a relatively high performance cost. For example, if you plan on having multiple chains one below the other, consider instead, applying just one chain and using it as a reference to constrain all other elements to the ones that match their position in that one chain. It may provide increased performance with no significant changes in the layout output.

Alternatively, consider if other helpers such as ConstraintLayoutScope.createGrid can accomplish the same layout.

See

Barriers

Barriers take a set of ConstrainedLayoutReferences and creates the most further point in a given direction where other ConstrainedLayoutReference can constrain to.

This is useful in situations where elements in a layout may have different sizes but you want to always constrain to the largest item, for example, if you have a text element on top of another and want an image to always be constrained to the end of them:

val (textRef0, textRef1, imageRef) = createRefs()

// Creates a point at the furthest end anchor from the elements in the barrier
val endTextsBarrier = createEndBarrier(textRef0, textRef1)

Text(
modifier = Modifier.constrainAs(textRef0) {
centerTo(parent)
},
text = "Hello, World!"
)
Text(
modifier = Modifier.constrainAs(textRef1) {
top.linkTo(textRef0.bottom)
start.linkTo(textRef0.start)
},
text = "Foo Bar"
)
Image(
modifier = Modifier.constrainAs(imageRef) {
top.linkTo(textRef0.top)
bottom.linkTo(textRef1.bottom)

// Image will always be at the end of both texts, regardless of their size
start.linkTo(endTextsBarrier, margin = 8.dp)
},
imageVector = Icons.Default.Android,
contentDescription = null
)

Be careful not to constrain a ConstrainedLayoutReference to a barrier that references it or that depends on it indirectly. This creates a cyclic dependency that results in unsupported layout behavior.

See

Tip: If you notice that you are creating many different constraints based on State variables or configuration changes, consider using the ConstraintSet pattern instead, makes it clearer to distinguish different layouts and allows you to automatically animate the layout when the provided ConstraintSet is different.

Parameters
modifier: Modifier = Modifier

Modifier to apply to this layout node.

optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD

Optimization flags for ConstraintLayout. The default is Optimizer.OPTIMIZATION_STANDARD.

animateChangesSpec: AnimationSpec<Float>? = null

Null by default. Otherwise, ConstraintLayout will animate the layout if there were any changes on the constraints during recomposition using the given AnimationSpec. If there's a change while the layout is still animating, the current animation will complete before animating to the latest changes. For more control in the animation consider using MotionLayout instead.

noinline finishedAnimationListener: (() -> Unit)? = null

Lambda called whenever an animation due to animateChangesSpec finishes.

crossinline content: @Composable ConstraintLayoutScope.() -> Unit

Content of this layout node.

@Composable
inline fun ConstraintLayout(
    constraintSet: ConstraintSet,
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    animateChangesSpec: AnimationSpec<Float>? = null,
    noinline finishedAnimationListener: (() -> Unit)? = null,
    crossinline content: @Composable () -> Unit
): Unit

Layout that positions its children according to the constraints between them.

This Composable of ConstraintLayout takes a ConstraintSet where the layout is defined using references and constraints.

Layouts referenced in the given constraintSet can be bound to immediate child Composables using Modifier.layoutId, where the given layoutIds match each named reference.

So, a simple layout with a text in the middle and an image next to it may be declared like this:

// IDs
val textId = "text"
val imageId = "image"

// Layout definition with references and constraints
val constraintSet = remember {
ConstraintSet {
val (textRef, imageRef) = createRefsFor(textId, imageId)
constrain(textRef) {
centerTo(parent)
}
constrain(imageRef) {
centerVerticallyTo(textRef)
start.linkTo(textRef.end, margin = 8.dp)
}
}
}

// ConstraintLayout uses our given ConstraintSet
ConstraintLayout(
constraintSet = constraintSet,
modifier = Modifier.fillMaxSize()
) {
// References are bound to Composables using Modifier.layoutId(Any)
Text(
modifier = Modifier.layoutId(textId),
text = "Hello, World!"
)
Image(
modifier = Modifier.layoutId(imageId),
imageVector = Icons.Default.Android,
contentDescription = null
)
}

See ConstraintSet to learn more on how to declare layouts using constraints.

Handling of ConstraintSet objects

You typically want to remember declared ConstraintSets, to avoid unnecessary allocations on recomposition, if the ConstraintSetScope block consumes any State variables, then something like remember { derivedStateOf { ConstraintSet { ... } } } would be more appropriate.

However, note in the example above that our ConstraintSet is constant, so we can declare it at a top level, improving overall Composition performance:

private const val TEXT_ID = "text"
private const val IMAGE_ID = "image"
private val mConstraintSet by lazy(LazyThreadSafetyMode.NONE) {
ConstraintSet {
val (textRef, imageRef) = createRefsFor(TEXT_ID, IMAGE_ID)
constrain(textRef) {
centerTo(parent)
}
constrain(imageRef) {
centerVerticallyTo(textRef)
start.linkTo(textRef.end, margin = 8.dp)
}
}
}


@Preview
@Composable
fun
ConstraintSetExample() {
ConstraintLayout(
constraintSet = mConstraintSet,
modifier = Modifier.fillMaxSize()
) {
Text(
modifier = Modifier.layoutId(TEXT_ID),
text = "Hello, World!"
)
Image(
modifier = Modifier.layoutId(IMAGE_ID),
imageVector = Icons.Default.Android,
contentDescription = null
)
}
}

This pattern (as opposed to defining constraints with ConstraintLayoutScope.constrainAs) is preferred when you want different layouts to be produced on different State variables or configuration changes. As it makes it easier to create distinguishable layouts, for example when building adaptive layouts based on Window size class:

private const val NAV_BAR_ID = "navBar"
private const val CONTENT_ID = "content"

private val compactConstraintSet by lazy(LazyThreadSafetyMode.NONE) {
ConstraintSet {
val (navBarRef, contentRef) = createRefsFor(NAV_BAR_ID, CONTENT_ID)

// Navigation bar at the bottom for Compact devices
constrain(navBarRef) {
width = Dimension.percent(1f)
height = 40.dp.asDimension()
bottom.linkTo(parent.bottom)
}

constrain(contentRef) {
width = Dimension.percent(1f)
height = Dimension.fillToConstraints

top.linkTo(parent.top)
bottom.linkTo(navBarRef.top)
}
}
}

private val mediumConstraintSet by lazy(LazyThreadSafetyMode.NONE) {
ConstraintSet {
val (navBarRef, contentRef) = createRefsFor(NAV_BAR_ID, CONTENT_ID)

// Navigation bar at the start on Medium class devices
constrain(navBarRef) {
width = 40.dp.asDimension()
height = Dimension.percent(1f)

start.linkTo(parent.start)
}

constrain(contentRef) {
width = Dimension.fillToConstraints
height = Dimension.percent(1f)

start.linkTo(navBarRef.end)
end.linkTo(parent.end)
}
}
}

@Composable
fun MyAdaptiveLayout(
windowWidthSizeClass: WindowWidthSizeClass
) {
val constraintSet = if (windowWidthSizeClass == WindowWidthSizeClass.Compact) {
compactConstraintSet
}
else {
mediumConstraintSet
}
ConstraintLayout(
constraintSet = constraintSet,
modifier = Modifier.fillMaxSize()
) {
Box(Modifier.background(Color.Blue).layoutId(NAV_BAR_ID))
Box(Modifier.background(Color.Red).layoutId(CONTENT_ID))
}
}

Animate Changes

When using multiple discrete ConstraintSets, you may pass non-null object to animateChangesSpec. With this, whenever ConstraintLayout is recomposed with a different ConstraintSet (by equality), it will animate all its children using the given AnimationSpec.

On the example above, using animateChangesSpec would result on the layout being animated when the device changes to non-compact window class, typical behavior in some Foldable devices.

If more control is needed, we recommend using MotionLayout instead, which has a very similar pattern through the MotionScene object.

Parameters
constraintSet: ConstraintSet

The ConstraintSet that describes the expected layout, defined references should be bound to Composables with Modifier.layoutId.

modifier: Modifier = Modifier

Modifier to apply to this layout node.

optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD

Optimization flags for ConstraintLayout. The default is Optimizer.OPTIMIZATION_STANDARD.

animateChangesSpec: AnimationSpec<Float>? = null

Null by default. Otherwise, ConstraintLayout will animate the layout if a different ConstraintSet is provided on recomposition using the given AnimationSpec. If there's a change in ConstraintSet while the layout is still animating, the current animation will complete before animating to the latest changes. For more control in the animation consider using MotionLayout instead.

noinline finishedAnimationListener: (() -> Unit)? = null

Lambda called whenever an animation due to animateChangesSpec finishes.

crossinline content: @Composable () -> Unit

Content of this layout node.

ConstraintLayout

@Composable
inline fun ConstraintLayout(
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    animateChanges: Boolean = false,
    animationSpec: AnimationSpec<Float> = tween<Float>(),
    noinline finishedAnimationListener: (() -> Unit)? = null,
    crossinline content: @Composable ConstraintLayoutScope.() -> Unit
): Unit

ConstraintLayout

@Composable
inline fun ConstraintLayout(
    constraintSet: ConstraintSet,
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    animateChanges: Boolean = false,
    animationSpec: AnimationSpec<Float> = tween<Float>(),
    noinline finishedAnimationListener: (() -> Unit)? = null,
    crossinline content: @Composable () -> Unit
): Unit