Constraint Layout in Jetpack Compose
introduction
I'll show you how to use Constraint Layout to build complex UIs with less code.
In Jetpack Compose, ConstraintLayout
there is indeed a powerful layout system that allows you to create complex and responsive layouts using the relative position of assemblies.
ConstraintLayout
Here are some of the advantages of using Jetpack Compose :
-
Complex Layouts : Ideal
when you need to create complex and custom layouts that cannot be easily achieved using simple rows and columns.ConstraintLayout
This complex layout can be achieved by usingConstraintLayout
precisely positioned components instead of nesting multiple rows and columns. -
Relative positioning :
You can use constraints to position components relatively. This is useful when you want one assembly to be adjacent or aligned with another assembly. This relative placement can help you achieve more complex UI designs. -
Performance considerations :
Although performance issues in nested view hierarchies are more important in View systems, Jetpack Compose is designed to handle deep layout hierarchies efficiently. Therefore,ConstraintLayout
the main advantage in Compose is not performance optimization, but improving code readability and maintaining a clear structure in the layout.
Get started with ConstraintLayout
Add dependencies to build.gradle(:app):
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
Use ConstraintLayout :
-
Create a reference: Create a reference for each assembly you want to position in the ConstraintLayout. You can create a reference using
createRefs()
a function or through a separate assemblycreateRefFor()
. -
Specify constraints: Use the modifier on each assembly you want to target
constrainAs()
. Pass the corresponding reference as a parameterconstrainAs()
. -
In the lambda, use
linkTo()
the etc. method to specify constraints for the assembly. -
Using "parent" references: You can also use parent references to specify constraints relative to the ConstraintLayout itself. This positions the assembly relative to the layout boundaries.
-
ConstraintSet:
ConstraintSet
Allows you to create and save constraints, applying them to existing onesConstraintLayout
.
For these cases you can use it in different ways ConstraintLayout
:
-
will be
ConstraintSet
passed as a parameterConstraintLayout
. -
Use
layoutId
the modifier toConstraintSet
assign the reference created in to the assembly.
For example :
- Use the
createRefs
,constrainAs
andlinkTo
modifiers.
@Composable
fun ConstraintLayoutContent() {
ConstraintLayout(Modifier.padding(16.dp)) {
// Create references for the composables to constrain
val (button, text, progressbar) = 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)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
) {
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)
}
)
LinearProgressIndicator(
progress = 0.60f,
modifier = Modifier.height(10.dp).constrainAs(progressbar) {
start.linkTo(text.end, margin = 8.dp)
top.linkTo(text.top)
end.linkTo(parent.end)
bottom.linkTo(text.bottom)
}
)
}
}
- Use
createRefFor
,linkTo
andConstraintSet
to build dynamic layouts.
@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, modifier = Modifier.padding(16.dp)) {
Button(
onClick = {
/* Do something */ },
modifier = Modifier.layoutId("button")
) {
Text("Button")
}
Text("Text", Modifier.layoutId("text"))
LinearProgressIndicator(
progress = 0.60f,
modifier = Modifier.height(10.dp).layoutId("progress_bar")
)
}
}
}
private fun decoupledConstraints(margin: Dp): ConstraintSet {
return ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
val progressBar = createRefFor("progress_bar")
constrain(button) {
top.linkTo(parent.top, margin = margin)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
constrain(text) {
top.linkTo(button.bottom, margin = margin)
}
constrain(progressBar){
start.linkTo(text.end, margin = margin)
top.linkTo(text.top)
end.linkTo(parent.end)
bottom.linkTo(text.bottom)
}
}
}
ConstraintLayoutConcept
Guideline :
A guideline is a small visual aid for designing layouts. Assemblies can be constrained to reference lines. dp
Guides are useful for positioning elements at specific or percentage positions within a parent assembly .
ConstraintLayout allows you to define horizontal and vertical guides, which are guides used for alignment or spacing. Guides are especially helpful when creating responsive layouts that adapt to different screen sizes and orientations.
The two horizontal guides are top and bottom, and the two vertical guides 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 guide, use createGuidelineFrom*
and the desired guide type. This creates a reference that can be Modifier.constrainAs()
used within the block.
Note: Consider using the Spacer component to achieve similar effects in rows and columns.
Barrier :
Barriers reference multiple assemblies to create virtual guidance lines based on the most extreme components on a given side. They allow you to create dynamic barriers that adjust their position based on the visibility of certain components. This is useful for handling situations where the visibility of one assembly affects the layout of other assemblies.
To create a barrier, use createTopBarrier()
(or: createBottomBarrier()、createEndBarrier()、createStartBarrier()
) and provide a reference that should form the barrier.
ConstraintLayout {
val constraintSet = ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
val topBarrier = createTopBarrier(button, text)
}
}
Barrier can be Modifier.constrainAs()
used within blocks.
Note: Consider using intrinsic measurement methods to achieve similar effects in rows and columns.
Chains :
Chains provide group-like behavior on a single axis (horizontal or vertical). The other axis can be constrained independently.
Chains are used to align and distribute assemblies in rows or columns. You can create linear chains where assemblies are connected to each other and the chains can be evenly spaced or aligned as desired.
To create a chain, use 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)
}
}
Chains can Modifier.constrainAs()
be used in blocks.
ChainStyles
Chains can be configured using different ones , which determine how the space around the assembly is treated, for example:
ChainStyle.Spread
: Space is evenly distributed between all assemblies, including free space before the first assembly and after the last assembly.ChainStyle.SpreadInside
: Space is evenly distributed among all assemblies, without any free space before the first assembly or after the last assembly.ChainStyle.Packed
: The space is distributed before the first assembly and after the last assembly, the assemblies are packed closely together with no space between each other.
Note: Consider using traditional
Rows
andColumns
differentArrangements
to achieve a similar effect to chains in ConstraintLayout.
Example :
@Composable
fun IndianFlagScreen() {
val constraints = ConstraintSet {
val orangeBox = createRefFor("orangebox")
val greenBox = createRefFor("greenbox")
val circle = createRefFor("circle")
constrain(orangeBox) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(circle.top)
width = Dimension.fillToConstraints
height = Dimension.value(260.dp)
}
constrain(circle) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.value(100.dp)
height = Dimension.value(100.dp)
}
constrain(greenBox) {
top.linkTo(circle.bottom)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
height = Dimension.value(260.dp)
}
createVerticalChain(orangeBox, circle, greenBox, chainStyle = ChainStyle.SpreadInside)
}
ConstraintLayout(constraints, modifier = Modifier.background(Color.White).fillMaxSize()) {
Box(
modifier = Modifier
.background(Color(0xFFFB8C00))
.layoutId("orangebox")
)
Box(
modifier = Modifier
.clip(CircleShape)
.background(Color.Blue)
.layoutId("circle")
)
Box(
modifier = Modifier
.background(Color(0xFF2EB734))
.layoutId("greenbox")
)
}
}
in conclusion
Jetpack Compose ConstraintLayout
is a versatile tool for creating complex and responsive layouts. It is particularly useful when you need precise control over the relative position of components or want to improve code readability and reduce the number of lines of code by avoiding too many nested Rows
sums Columns
.