interface ConstraintSet


Immutable description of the constraints used to layout the children of a ConstraintLayout.

Layout items are defined with ConstraintSetScope.createRefFor, each layout item must be defined with a unique ID from other items in the same scope:

val textRef = createRefFor("text")
val imageRef = createRefFor("image")

You may also use ConstraintSetScope.createRefsFor to declare up to 16 items at a time using the destructuring declaration pattern:

val (textRef, imageRef) = createRefsFor("text", "image")

Individual constraints are defined with ConstraintSetScope.constrain. Where you can tell each layout reference how to constrain to other references including the parent:

constrain(textRef) {
centerTo(parent)
}
constrain(imageRef) {
centerVerticallyTo(textRef)
start.linkTo(textRef.end, margin = 8.dp)
}

Here, we constrain the textRef to the center of the parent, while the image is centered vertically to textRef and is horizontally placed to the right of its end anchor with a margin (keep in mind, when using center..., start or end the layout direction will automatically change in RTL locales).

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

In the ConstraintLayout or MotionLayout Composables, the children must be bound using Modifier.layoutId.

So, the whole snippet with ConstraintLayout would look like this:

val textId = "text"
val imageId = "image"
ConstraintLayout(
constraintSet = ConstraintSet {
val (textRef, imageRef) = createRefsFor(textId, imageId)
constrain(textRef) {
centerTo(parent)
}
constrain(imageRef) {
centerVerticallyTo(textRef)
start.linkTo(textRef.end, margin = 8.dp)
}
},
modifier = Modifier.fillMaxSize()
) {
Text(
modifier = Modifier.layoutId(textId),
text = "Hello, World!"
)
Image(
modifier = Modifier.layoutId(imageId),
imageVector = Icons.Default.Android,
contentDescription = null
)
}

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 ConstraintSetScope 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) = createRefsFor(textId)
val vG = createGuidelineFromStart(fraction = 0.3f)

constrain(textRef) {
centerVerticallyTo(parent)
centerAround(vG)
}

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) = createRefsFor("text0", "text1", "text2")
createHorizontalChain(textRef0, textRef1, textRef2, chainStyle = ChainStyle.Spread)

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

val (textRef0, textRef1, textRef2) = createRefsFor("text0", "text1", "text2")
createHorizontalChain(
textRef0,
textRef1.withChainParams(startMargin = 100.dp, endMargin = 100.dp),
textRef2,
chainStyle = ChainStyle.Spread
)

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) = createRefsFor("text0", "text1", "text2")
createHorizontalChain(
textRef0.withChainParams(weight = 1f),
textRef1.withChainParams(weight = 1f),
textRef2.withChainParams(weight = 1f),
chainStyle = ChainStyle.Spread
)

constrain(textRef0, textRef1, textRef2) {
width = Dimension.fillToConstraints
}

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

Also note that when using ConstraintSetScope you can apply the same constrains to multiple references at a time.

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 ConstraintSetScope.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) = createRefsFor("text0", "text1", "image")

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

constrain(textRef0) {
centerTo(parent)
}
constrain(textRef1) {
top.linkTo(textRef0.bottom)
start.linkTo(textRef0.start)
}

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

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

Summary

Public functions

Unit
applyTo(state: State, measurables: List<Measurable>)

Applies the ConstraintSet to a state.

open Unit
applyTo(transition: Transition, type: Int)
open Boolean
isDirty(measurables: List<Measurable>)
open ConstraintSet
override(name: String, value: Float)

Public functions

applyTo

fun applyTo(state: State, measurables: List<Measurable>): Unit

Applies the ConstraintSet to a state.

applyTo

Added in 1.1.0
open fun applyTo(transition: Transition, type: Int): Unit

isDirty

open fun isDirty(measurables: List<Measurable>): Boolean

override

Added in 1.1.0
open fun override(name: String, value: Float): ConstraintSet