ConstraintLayout
is a layout that allows you to place composables relative to
other composables on the screen. It is an alternative to using multiple nested
Row
, Column
, Box
and other custom layout elements. ConstraintLayout
is useful when implementing larger layouts with more complicated alignment
requirements.
Consider using ConstraintLayout
in the following scenarios:
- To avoid nesting multiple
Column
s andRow
s for positioning elements on screen to improve readability of code. - To position composables relative to other composables or to position composables based on guidelines, barriers or chains.
In the View system, ConstraintLayout
was the recommended way to create large
and complex layouts, as a flat view hierarchy was better for performance than
nested views are. However, this is not a concern in Compose, which is able to
efficiently handle deep layout hierarchies.
Get started with ConstraintLayout
To use ConstraintLayout
in Compose, you need to add this dependency in your
build.gradle
(in addition to the
Compose setup):
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
ConstraintLayout
in Compose works in the following way using a
DSL:
- Create references for each composable in the
ConstraintLayout
using thecreateRefs()
orcreateRefFor()
- 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 theConstraintLayout
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
.
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:
- Pass in a
ConstraintSet
as a parameter toConstraintLayout
. - Assign references created in the
ConstraintSet
to composables using thelayoutId
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
.
ConstraintLayout
concepts
ConstraintLayout
contains concepts such as guidelines, barriers and chains
that can help with positioning elements inside your Composable.
Guidelines
Guidelines are small visual helpers to design layouts with. Composables can be
constrained to a guideline. Guidelines are useful for positioning elements at a
certain dp
or percentage
inside the parent composable.
There are two different kinds of guidelines, vertical and horizontal. The two
horizontal ones are top
and bottom
, and the two vertical are start
and
end
.
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) }
To create a guideline, use createGuidelineFrom*
with the type of guideline
required. This creates a reference that can be used in the
Modifier.constrainAs()
block.
Barriers
Barriers reference multiple composables to create a virtual guideline based on the most extreme widget on the specified side.
To create a barrier, use createTopBarrier()
(or: createBottomBarrier()
,
createEndBarrier()
, createStartBarrier()
), and provide the references that
should make up the barrier.
ConstraintLayout { val constraintSet = ConstraintSet { val button = createRefFor("button") val text = createRefFor("text") val topBarrier = createTopBarrier(button, text) } }
The barrier can then be used in a Modifier.constrainAs()
block.
Chains
Chains provide group-like behavior in a single axis (horizontally or vertically) . The other axis can be constrained independently.
To create a chain, use either createVerticalChain
or createHorizontalChain
:
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) } }
The chain can then be used in the Modifier.constrainAs()
block.
A chain can be configured with different ChainStyles
, which decide how to deal
with the space surrounding a composable, such as:
ChainStyle.Spread
: Space is distributed evenly across all the composables, including free space before the first composable and after the last composable.ChainStyle.SpreadInside
: Space is distributed evenly across the all composables, without any free space before the first composable or after the last composable.ChainStyle.Packed
: Space is distributed before the first and after the last composable, composables are packed together without space in between each other.
Learn more
Learn more about ConstraintLayout
in Compose from the APIs in action in the
Compose samples that use ConstraintLayout
.
Recommended for you
- Note: link text is displayed when JavaScript is off
- Focus in Compose
- Kotlin for Jetpack Compose
- Compose layout basics