At its core, Jetpack Compose layouts implement a declarative approach to UI building, departing from the traditional imperative View system. The Jetpack Compose layouts system focuses on describing what your UI should look like for a given state, rather than manually constructing and manipulating view hierarchies.
The Jetpack Compose layouts system offers several key advantages:
The Box layout is one of the most fundamental Jetpack Compose layouts. It stacks its children on top of each other, similar to a FrameLayout in the View system. This makes Box in Jetpack Compose layouts ideal for overlapping elements like placing text over images or adding floating action buttons.
Box(
modifier = Modifier
.size(100.dp)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text("I'm centered in the Box")
Box(
modifier = Modifier
.size(40.dp)
.align(Alignment.TopEnd)
.background(Color.DarkGray)
)
}
Key Box properties in Compose:
The Box layout in Compose excels at:
The Column layout arranges its children vertically in a single column. This makes Column in Jetpack Compose layouts an essential tool for creating vertical lists, forms, and other UI elements that flow from top to bottom.
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("First item in Column")
Text("Second item in Column")
Text("Third item in Column")
}
Key Column properties in Compose:
The Column layout in Compose is particularly useful for:
The Row layout arranges its children horizontally, making it perfect for toolbar items, horizontal lists, and other side-by-side element arrangements. The Row in Jetpack Compose layouts is analogous to a horizontal LinearLayout in the View system.
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("Start")
Text("Center")
Text("End")
}
Key Row properties in Compose:
The Row layout in Compose excels at:
The LazyColumn and LazyRow Jetpack Compose layouts are optimized for displaying large lists of items. Unlike their standard counterparts, LazyColumn in Jetpack Compose layouts and LazyRow in Jetpack Compose layouts only render visible items, making them highly efficient for long scrollable lists.
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(100) { index ->
Text(
text = "Item #$index",
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.background(Color.LightGray)
)
}
}
Key LazyColumn/LazyRow properties in Compose:
LazyColumn and LazyRow in Compose are essential for:
ConstraintLayout in Jetpack Compose layouts provides a flexible way to position and size elements according to constraints, similar to the View system's ConstraintLayout. It's particularly useful for complex UIs where elements need to be positioned relative to each other in Jetpack Compose layouts.
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
val (title, subtitle, image) = createRefs()
Text(
text = "Title Text",
modifier = Modifier.constrainAs(title) {
top.linkTo(parent.top)
start.linkTo(parent.start)
}
)
Text(
text = "Subtitle goes here",
modifier = Modifier.constrainAs(subtitle) {
top.linkTo(title.bottom, margin = 8.dp)
start.linkTo(title.start)
}
)
Box(
modifier = Modifier
.size(50.dp)
.background(Color.Gray)
.constrainAs(image) {
top.linkTo(parent.top)
end.linkTo(parent.end)
}
)
}
Key ConstraintLayout features in Compose:
ConstraintLayout in Compose is ideal for:
Scaffold in Compose provides a framework for implementing the basic material design layout structure. It manages common UI elements like AppBar, BottomBar, FloatingActionButton, and Drawer, arranging them according to material guidelines.
Scaffold(
topBar = {
TopAppBar(
title = { Text("Scaffold Example") },
backgroundColor = MaterialTheme.colors.primary
)
},
bottomBar = {
BottomAppBar {
Text("Bottom app bar", modifier = Modifier.padding(8.dp))
}
},
floatingActionButton = {
FloatingActionButton(onClick = { /* Handle FAB click */ }) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
},
floatingActionButtonPosition = FabPosition.End
) { innerPadding ->
// Main content
Box(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("Main content goes here")
}
}
Key Scaffold properties in Compose:
Scaffold in Compose is excellent for:
Intrinsic measurements allow layout elements to measure their children without actually placing them. This can help solve complex layout problems where elements need to size themselves based on other elements.
Intrinsic measurement functions in Compose:
Row(modifier = Modifier.fillMaxWidth()) {
Text(
text = "Label:",
modifier = Modifier
.alignBy { it.measuredHeight }
.padding(end = 8.dp)
)
Box(
modifier = Modifier
.weight(1f)
.border(1.dp, Color.Gray)
.padding(8.dp)
.alignBy { it.measuredHeight }
) {
Text("Value that might wrap to multiple lines if it gets too long")
}
}
Jetpack Compose allows creating custom layouts when standard options don't meet your needs. You can implement the Layout
composable to create your own layout logic:
@Composable
fun ChipsLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Custom layout logic
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
layout(constraints.maxWidth, constraints.maxHeight) {
var xPosition = 0
var yPosition = 0
var maxHeight = 0
placeables.forEach { placeable ->
if (xPosition + placeable.width > constraints.maxWidth) {
xPosition = 0
yPosition += maxHeight
maxHeight = 0
}
placeable.place(x = xPosition, y = yPosition)
xPosition += placeable.width
maxHeight = maxOf(maxHeight, placeable.height)
}
}
}
}
ModifierLocal provides a way to implicitly pass data down the modifier chain, similar to CompositionLocal but for modifiers. This advanced technique can be useful for creating complex custom modifiers.
SubcomposeLayout allows measuring and placing composables multiple times with different constraints, enabling more complex layout logic than standard layout composables.
SubcomposeLayout { constraints ->
// Measure the title
val titlePlaceables = subcompose("title") {
Text("Title")
}.map { it.measure(constraints) }
// Use title's height to constrain the content
val contentConstraints = constraints.copy(
maxHeight = constraints.maxHeight - titlePlaceables[0].height
)
val contentPlaceables = subcompose("content") {
Text("Content goes here with remaining space")
}.map { it.measure(contentConstraints) }
layout(constraints.maxWidth, constraints.maxHeight) {
titlePlaceables[0].place(0, 0)
contentPlaceables[0].place(0, titlePlaceables[0].height)
}
}
Creating responsive UIs that adapt to different screen sizes is crucial for modern Android applications. Jetpack Compose provides several approaches to responsive design:
Using WindowSizeClass from the Compose Material3 library helps adapt layouts based on device size categories:
val windowSizeClass = calculateWindowSizeClass(activity)
when (windowSizeClass.widthSizeClass) {
WindowWidthSizeClass.Compact -> {
// Phone layout (single column)
Column { /* Content */ }
}
WindowWidthSizeClass.Medium -> {
// Tablet layout (two columns)
Row { /* Content */ }
}
WindowWidthSizeClass.Expanded -> {
// Desktop layout (multi-column)
Row { /* More complex layout */ }
}
}
BoxWithConstraints in Compose gives access to layout constraints directly in composition, enabling conditional layouts:
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
if (maxWidth < 600.dp) {
// Narrow layout
Column { /* Vertical layout for phones */ }
} else {
// Wide layout
Row { /* Horizontal layout for tablets/desktop */ }
}
}
@Composable
fun SocialFeed(posts: List<Post>) {
LazyColumn {
items(posts) { post ->
PostCard(
post = post,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
}
}
}
@Composable
fun PostCard(post: Post, modifier: Modifier = Modifier) {
Card(
modifier = modifier,
elevation = 4.dp
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(post.authorImageRes),
contentDescription = "Author avatar",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = post.authorName,
style = MaterialTheme.typography.subtitle1
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(post.content)
if (post.imageRes != null) {
Spacer(modifier = Modifier.height(8.dp))
Image(
painter = painterResource(post.imageRes),
contentDescription = "Post image",
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Row {
Icon(
imageVector = Icons.Default.ThumbUp,
contentDescription = "Like"
)
Text(text = post.likes.toString())
}
Row {
Icon(
imageVector = Icons.Default.Comment,
contentDescription = "Comment"
)
Text(text = post.comments.toString())
}
Row {
Icon(
imageVector = Icons.Default.Share,
contentDescription = "Share"
)
Text(text = post.shares.toString())
}
}
}
}
}
@Composable
fun ProductGrid(products: List<Product>) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 160.dp),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(products) { product ->
ProductCard(product)
}
}
}
@Composable
fun ProductCard(product: Product) {
Card(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(0.7f),
elevation = 4.dp
) {
Column {
Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
) {
Image(
painter = painterResource(product.imageRes),
contentDescription = product.name,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
if (product.isOnSale) {
Surface(
color = MaterialTheme.colors.secondary,
shape = RoundedCornerShape(bottomEnd = 8.dp),
modifier = Modifier.align(Alignment.TopStart)
) {
Text(
text = "SALE",
modifier = Modifier.padding(4.dp),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSecondary
)
}
}
}
Column(modifier = Modifier.padding(8.dp)) {
Text(
text = product.name,
style = MaterialTheme.typography.subtitle1,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(4.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "$${product.price}",
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold
)
if (product.originalPrice != null) {
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "$${product.originalPrice}",
style = MaterialTheme.typography.body2,
textDecoration = TextDecoration.LineThrough,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
)
}
}
}
}
}
}