Instruction
Layouts in Compose
So far, you learned how to build basic user interfaces using composable functions. The next step is to build a more complex UI by arranging smaller composables and positioning them in a specific way. In XML, you use ViewGroup widgets like LinearLayout, RelativeLayout, or FrameLayout to measure and position children on the screen based on rules.
The compose equivalent of ViewGroup is Layout.
@Composable
inline fun Layout(
content: @Composable @UiComposable () -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
)
Layout contains two important parameters:
-
Content: The composable that holds the children of the
Layout. - MeasurePolicy: This is used to define the measuring and layout behavior.
Measuring and positioning elements can be difficult and prone to errors, so compose ships with predefined layouts to make your life easier.
These predefined layouts handle the elements’ positioning based on their internal logic.
Exploring Basic Layouts in Compose
Compose offers layouts that allow you to order items vertically, horizontally, and on top of one another. Compose also offers more complex layouts that handle UI elements like navigation drawers, etc. Internally, all these layouts use the measurePolicy property to position items.
In XML, if you wanted to order items horizontally or vertically you would use a LinearLayout. LinearLayout uses the orientation parameter to determine whether to stack items horizontally or vertically. Compose offers two layout composables to handle the different orientation for linear stacking, namely Row and Column.
Using Row
Row is the LinearLayout counterpart that handles stacking items horizontally. Here’s a look at the signature for Row.
@Composable
inline fun Row(
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit
)
Row accepts four parameters:
- Modifier: Controls the appearance and behavior of the row.
- HorizontalArrangement: Handles the horizontal arrangement of the row’s children.
- VerticalAlignment: Handles the vertical alignment of the row’s children.
- Content: The content of the row.
Row uses two parameters that are new to you, horizontalArrangement and verticalAlignment.
Arrangement is used to position children relative to one another. The available arrangement options are:
-
SpaceBetween:
Rowplaces each child with an equal amount of space without calculating spacing before the first child or after the last child. -
SpaceEvenly:
Rowplaces the children in an equal amount of space, but this time, it includes starting or ending spacing. -
SpaceAround:
Rowplaces children just likeSpaceEvenlybut reduces the space between consecutive children by half. -
Center, Start and End: The
Rowplaces children at the center, start or end without spaces between them.
Alignment is used to position the children in a specific way within the parent. In the case of rows, verticalAlignment aligns the children vertically in three possible ways:
- Top: Aligns the children to the top of the parent.
- CenterVertically: Aligns the children vertically in the center of the parent.
- Bottom: Aligns the children to the bottom of the parent.
Here’s what using a Row would look like:
@Composable
fun EmailUi(
emails: List<Email>,
selectedEmail: Email
) {
Row(
verticalAlignment = Alignment.Start,
horizontalArrangement = Arrangement.Top,
modifier = Modifier.fillMaxSize()
) {
EmailList(emails)
EmailDetail(selectedEmail)
}
}
The composable shown above shows a simple master-detail email layout, with the list of emails on the left and the details of each email on the right.
In the snippet, the row will arrange the contents from the top-left of the screen and will occupy the maximum space available.
Using Column
Column is the LinearLayout counterpart that handles stacking items vertically. Here’s a look at the signature for Column:
@Composable
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
)
Just like Row, Column accepts four parameters:
- Modifier: Controls the appearance and behavior of the Column.
- VerticalArrangement: Handles the vertical arrangement of the Column’s children.
- HorizontalAlignment: Handles the horizontal alignment of the Column’s children.
- Content: The content of the Column.
Looking closely at Column, you’ll notice that it swaps the arrangements and alignments. This means you can do all the same things inside the Column as in a Row, but with different orientations.
Here’s a sample usage of Column:
@Composable
fun UserDetailsCardColumn() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
Text("John Appleseed")
Text("32 years old")
Text("john@example.net")
}
}
The Column implementation above is similar to the one with Row except for the arrangement and alignments.
Using Box
Box is the FrameLayout counterpart and handles stacking items on top of one another relative to the parent. Box is helpful for cases where you need to show overlapping items such as dialogs or cards.
Here’s a look at the signature for Box:
@Composable
inline fun Box(
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit
)
Box accepts four arguments:
- Modifier: Modifier to control the appearance and behavior of the Box.
- ContentAlignment: To set the default alignment of the children.
-
PropagateMinConstraints: Defines whether the minimal constraints should be passed and used for the content, too. By default, the constraints of the
Box()won’t be taken into account when measuring the children. -
Content: The content of the
Box. With multiple children inside aBox, they’re rendered in the order you placed them inside.
When using Box the alignment can be set to any edge of the screen as well as relative to the center. The options available are:
- TopStart
- TopCenter
- TopEnd
- CenterStart
- Center
- CenterEnd
- BottomStart
- BottomCenter
- BottomEnd
Here’s a sample usage of Box:
@Composable
fun UserAvatar(user: User) {
Box {
Image(bitmap = user.image, contentDescription = "User image")
if(user.verified) {
Icon(Icons.Filled.Check, contentDescription = "Check mark")
}
}
}
The example above uses a Box to create a user avatar UI and shows a checked icon over the user’s image if the user is verified.