Jetpack Compose is Android’s recommended modern toolkit for building native user interfaces. It streamlines and speeds up UI development on Android. Effortlessly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.
ConstraintLayout allows you to create large, complex layouts without using nested view groups, keeping the view hierarchy flat. Like RelativeLayout, it positions views based on their relationships to other views and the parent layout. However, ConstraintLayout is more flexible and easier to use, especially with Android Studio’s Layout Editor.
Constraint layout allows us to create responsive UI’s across resolutions.
We are familiar with building UI using ConstraintLayout in XML let’s see how to do it in JetpackCompose.
Let’s start with a basic example.
First you need to add the following gradle dependency in the build.gradle file.
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
Vertical positioning:
Let’s write our first UI code
@Preview | |
@Composable | |
fun ConstraintLayoutView() { | |
var count by remember { mutableStateOf(0) } | |
ConstraintLayout(modifier = Modifier.fillMaxSize()) { | |
val (button, text) = createRefs() | |
Button( | |
onClick = { count++ }, | |
modifier = Modifier.constrainAs(button) { | |
width = Dimension.value(200.dp) | |
height = Dimension.value(56.dp) | |
top.linkTo(parent.top, margin = 16.dp) | |
bottom.linkTo(parent.bottom, margin = 16.dp) | |
start.linkTo(parent.start, margin = 16.dp) | |
end.linkTo(parent.end, margin = 16.dp) | |
} | |
) { | |
Text("Click") | |
} | |
Text( | |
"Count:${count}", | |
Modifier.constrainAs(text) { | |
top.linkTo(button.bottom, margin = 16.dp) | |
start.linkTo(button.start, margin = 16.dp) | |
end.linkTo(button.end, margin = 16.dp) | |
} | |
) | |
} | |
} |
Output of the above function:
In the above example we have used:
- @Preview to see preview of our UI.
- I used remember as I want this UI to remember the count value.
- I have added a Button and a TextView to the layout. I have given them the id’s button and text by this line val (button, text) = createRefs().
- I have placed the button in the centre of the screen and given height and width to the button.
- I have placed a TextView below this Button and on click of this button we will change the text.
To understand it in a much better way here is the XML layout for the above Composable function.
<androidx.constraintlayout.widget.ConstraintLayout | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<Button | |
android:id="@+id/button" | |
android:layout_width="200dp" | |
android:layout_height="56dp" | |
android:text="CLick" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/text" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
app:layout_constraintEnd_toEndOf="@+id/button" | |
app:layout_constraintStart_toStartOf="@+id/button" | |
app:layout_constraintTop_toBottomOf="@+id/button" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
Horizontal positioning:
Now let’s try using another example. Let’s say we want to achieve something like below.
We want to have two TextViews next to each other.
@Preview | |
@Composable | |
fun ConstraintLayoutView() { | |
ConstraintLayout(modifier = Modifier.fillMaxSize()) { | |
val (textTitle, textMore) = createRefs() | |
Text( | |
"This is a long title This is a long title This is a long title This is a long title", | |
modifier = Modifier.constrainAs(textTitle) { | |
top.linkTo(parent.top, margin = 8.dp) | |
start.linkTo(parent.start, margin = 8.dp) | |
end.linkTo(textMore.start, 8.dp) | |
width = Dimension.fillToConstraints | |
} | |
) | |
Text( | |
"Show More", | |
color = Color.Blue.copy(alpha = 0.5f), | |
modifier = Modifier.constrainAs(textMore) { | |
top.linkTo(parent.top, margin = 8.dp) | |
end.linkTo(parent.end, margin = 8.dp) | |
} | |
) | |
} | |
} |
In the above example:
- I have added two text views with id’s textTitle and textMore.
- I have added textViewMore at the top end with respect to the parent.
- I have added textViewTitle at the top of the parent and the end is relative to textMore at the start of it.
- I have given width width = Dimension.fillToConstraints to textTitle which is equivalent to 0dp in XML layout.
Here is the XML layout equivalent for the above example to understand better:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<TextView | |
android:id="@+id/textTitle" | |
android:layout_width="0dp" | |
android:layout_height="wrap_content" | |
android:layout_marginStart="8dp" | |
android:layout_marginTop="8dp" | |
android:layout_marginEnd="8dp" | |
android:text="This is a long title This is a long title This is a long title This is a long title" | |
app:layout_constraintEnd_toStartOf="@+id/textMore" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/textMore" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginTop="8dp" | |
android:layout_marginEnd="8dp" | |
android:text="Show More" | |
android:textColor="@android:color/holo_blue_dark" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
Percentage ratio:
Consider we want to add an ImageView with width match parent and height equivalent to 40% percent of the total screen height.
Here is the code
Job Offers
@Preview | |
@Composable | |
fun ConstraintLayoutView() { | |
ConstraintLayout(modifier = Modifier.fillMaxSize()) { | |
val (image, textMore) = createRefs() | |
Image( | |
painter = painterResource(id = R.drawable.ic_launcher_background), | |
contentScale = ContentScale.Crop, | |
contentDescription = "", | |
modifier = Modifier.constrainAs(image) { | |
top.linkTo(parent.top, margin = 8.dp) | |
start.linkTo(parent.start, margin = 8.dp) | |
end.linkTo(parent.end, 8.dp) | |
height = Dimension.percent(0.4f) | |
width = Dimension.matchParent | |
} | |
) | |
} | |
} |
Output:
In the above example
- height = Dimension.percent(0.4f) sets height in ratio with the Screen height
- width = Dimension.matchParent sets width match parent
The XML equivalent of the above example would be:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<ImageView | |
android:layout_width="match_parent" | |
android:layout_height="0dp" | |
android:scaleType="centerCrop" | |
android:src="@drawable/ic_launcher_background" | |
app:layout_constraintHeight_percent="0.4" | |
app:layout_constraintTop_toTopOf="parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
Guideline and Barrier:
Consider we have two TextViews both of them can have text of variable length and below these two TextViews I want to place a Button. We can not not place a button relative to any TextView as their lengths may vary.
Following is the code to Achieve UI like above:
@Preview | |
@Composable | |
fun ConstraintLayoutView() { | |
ConstraintLayout(modifier = Modifier.fillMaxSize()) { | |
val (text1, text2, button) = createRefs() | |
val guideline = createGuidelineFromStart(0.5f) | |
val barrier = createBottomBarrier(text1, text2) | |
Button( | |
onClick = { }, | |
modifier = Modifier.constrainAs(button) { | |
width = Dimension.value(200.dp) | |
height = Dimension.value(56.dp) | |
top.linkTo(barrier, margin = 8.dp) | |
start.linkTo(parent.start, margin = 8.dp) | |
end.linkTo(parent.end, margin = 8.dp) | |
} | |
) { | |
Text("I am below two views") | |
} | |
Text( | |
"this is a variable lenght text", | |
Modifier.constrainAs(text1) { | |
top.linkTo(parent.top, margin = 8.dp) | |
start.linkTo(parent.start, margin = 8.dp) | |
end.linkTo(guideline, margin = 8.dp) | |
width = Dimension.fillToConstraints | |
} | |
) | |
Text( | |
"this is another textview with variable lenght", | |
Modifier.constrainAs(text2) { | |
top.linkTo(parent.top, margin = 8.dp) | |
start.linkTo(guideline, margin = 8.dp) | |
end.linkTo(parent.end, margin = 8.dp) | |
width = Dimension.fillToConstraints | |
} | |
) | |
} | |
} |
In above example
- I have created text1, text2 and button.
- I have created a horizontal guideline at 50 percent of the width val guideline = createGuidelineFromStart(0.5f) and a barrier below text1 and text2 val barrier = createBottomBarrier(text1, text2).
- I have placed text1 and text2 relative to parent and guideline.
- I have placed a button below the barrier where text1 and text2 ends.
The XML equivalent of the above example would be:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<androidx.constraintlayout.widget.Guideline | |
android:id="@+id/guideline" | |
android:orientation="vertical" | |
app:layout_constraintGuide_percent="0.5" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content"/> | |
<TextView | |
android:text="this is a variable lenght text" | |
android:layout_marginTop="8dp" | |
android:layout_marginEnd="8dp" | |
android:layout_marginStart="8dp" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintEnd_toStartOf="@+id/guideline" | |
app:layout_constraintTop_toTopOf="parent" | |
android:id="@+id/text1" | |
android:layout_width="0dp" | |
android:layout_height="wrap_content"/> | |
<TextView | |
android:text="this is another textview with variable lenght" | |
android:layout_marginTop="8dp" | |
android:layout_marginEnd="8dp" | |
android:layout_marginStart="8dp" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toEndOf="@+id/guideline" | |
app:layout_constraintTop_toTopOf="parent" | |
android:id="@+id/text2" | |
android:layout_width="0dp" | |
android:layout_height="wrap_content"/> | |
<androidx.constraintlayout.widget.Barrier | |
android:id="@+id/barrier" | |
app:barrierDirection="bottom" | |
app:constraint_referenced_ids="text1,text2" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content"/> | |
<Button | |
android:id="@+id/button" | |
android:layout_marginTop="8dp" | |
android:layout_marginEnd="8dp" | |
android:text="I am below two views" | |
android:layout_marginStart="8dp" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintTop_toBottomOf="@+id/barrier" | |
android:layout_width="200dp" | |
android:layout_height="56dp"/> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
Conclusion:
ConstraintLayout in Jetpack Compose simplifies Android UI creation with precise layout control and flexibility. Its declarative approach empowers developers to compose complex UIs effortlessly. Seamlessly integrating Material Design principles ensures consistent and appealing interfaces. By leveraging ConstraintLayout, developers streamline UI development for faster iteration and improved user experiences. Its ongoing evolution promises continued innovation, shaping the future of Android app design uniquely.
This article is previously published on proandroiddev.com