Google is committed to advancing racial equity for Black communities. See how.

Layouts in Compose

Jetpack Compose makes it much easier to design and build your app's UI. This document explains some of the building blocks Compose provides to help you lay out your UI elements, and shows you how to build more specialized layouts when you need them.

Composable functions are the basic building block of Compose. A composable function is a function that describes some part of your UI. The function takes some input and generates what's shown on the screen. For more information about composables, take a look at the Compose mental model documentation.

A Composable function might emit several UI elements. However, if you don't provide guidance on how they should be arranged, Compose might arrange the elements in a way you don't like. For example, this code generates two text elements:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Without guidance on how you want them arranged, Compose stacks the text elements on top of each other, making them unreadable:

Two text elements drawn on top of each other, making the text unreadable

Compose provides a collection of ready-to-use layouts to help you arrange your UI elements, and makes it easy to define your own, more-specialized layouts.

Standard layout components

In many cases, you can just use Compose's standard layout elements.

Use Column to place items vertically on the screen.

@Composable
fun ArtistCard() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Two text elements arranged in a column layout, so the text is readable

Similarly, use Row to place items horizontally on the screen. Both Column and Row support configuring the gravity of the elements they contain.

@Composable
fun ArtistCard(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image( /*...*/ )
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

Shows a more complex layout, with a small graphic next to a column of text elements

Use Stack to put one element on top of another.

Compares three simple layout composables: column, row, and stack

Often these building blocks are all you need. You can write your own composable function to combine these layouts into a more elaborate layout that suits your app.

Each of these basic layouts defines its own gravity settings, specifying how the elements should be arranged. To configure these elements, use modifiers.

Modifiers

Modifiers allow you to tweak how a composable is presented. Modifiers let you do these sorts of things:

  • Change the composable's behavior and appearance
  • Add information, like accessibility labels
  • Process user input
  • Add high-level interactions, like making an element clickable, scrollable, draggable, or zoomable

Modifiers are standard Kotlin objects. Create a modifier by calling one of the Modifier class functions. You can chain these functions together to compose them:

@Composable
fun ArtistCard(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.preferredSize(padding))
        Card(elevation = 4.dp) { /*...*/  }
    }
}

A still more complex layout, using modifiers to change how the graphics are arranged and which areas respond to user input

In the code above, notice different modifier functions used together.

  • clickable() makes a composable react to user input.
  • padding() puts space around an element.
  • fillMaxWidth() makes the composable fill the maximum width given to it from its parent.
  • preferredSize() specifies an element's preferred width and height.

The order of modifier functions is significant. Since each function makes changes to the Modifier returned by the previous function, the sequence affects the final result. Let's see an example of this:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

The entire area, including the padding around the edges, responds to clicks

In the code above the whole area is clickable, including the surrounding padding, because the padding modifier has been applied after the clickable modifier. If the modifiers are applied in the other order, the space added by padding does not react to user input:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

The padding around the edge of the layout no longer responds to clicks

Scrollable layouts

Use ScrollableRow or ScrollableColumn to make the elements inside a Row or Column scroll.

@Composable
fun Feed(
    feedItems: List<Artist>,
    onSelected: (Artist) -> Unit
) {
    ScrollableColumn(Modifier.fillMaxSize()) {
        feedItems.forEach {
            ArtistCard(it, onSelected)
        }
    }
}

Several similar layouts in a scrollable column

This approach works well if there are few elements to show, but can quickly become a performance concern for large data sets. To show only part of the elements visible on the screen, use LazyColumnFor or LazyRowFor.

@Composable
fun Feed(
    feedItems: List<Artist>,
    onSelected: (Artist) -> Unit
) {
    Surface(Modifier.fillMaxSize()) {
        LazyColumnFor(feedItems) { item ->
            ArtistCard(item, onSelected)
        }
    }
}

Built-in Material components

Compose's highest level of UI abstraction is Material Design. Compose provides a large variety of composables out of the box to make UI building easy. Elements like Drawer, FloatingActionButton, and TopAppBar are all provided.

Material components make heavy use of slot APIs, a pattern Compose introduces to bring in a layer of customization on top of composables. Slots leave an empty space in the UI for the developer to fill as they wish. For example, these are the slots that you can customize in a TopAppBar:

Shows the slots in an app bar, where you can add UI elements

Composables usually take a content composable lambda ( content: @Composable () -> Unit). Slot APIs expose multiple content parameters for specific uses. For example, TopAppBar allows you to provide the content for title, navigationIcon, and actions.

The most high-level composable with Material is Scaffold. Scaffold allows you to implement a UI with the basic Material Design layout structure. Scaffold provides slots for the most common top-level Material components, such as TopAppBar, BottomAppBar, FloatingActionButton, and Drawer. By using Scaffold, it's easy to make sure these components are properly positioned and work together correctly.

Shows a layout that uses Scaffold to arrange elements in a way consistent with Material Design

@Composable
fun HomeScreen( /*...*/ ) {
    Scaffold (
        drawerContent = { /*...*/ },
        topBar = { /*...*/ },
        bodyContent = { /*...*/ }
    )
}

ConstraintLayout

ConstraintLayout can help place composables relative to others on the screen, and is an alternative to using multiple Row, Column, and Stack elements. ConstraintLayout is useful when implementing larger layouts with more complicated alignment requirements.

ConstraintLayout in Compose works with a DSL:

  • References are created using createRefs() or createRefFor(), and each composable in ConstraintLayout needs to have a reference associated with it.
  • 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 the ConstraintLayout 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.

Shows a button and a text element arranged in a ConstraintLayout

For more examples of how to work with ConstraintLayout, try the layouts codelab.

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:

  1. Pass in a ConstraintSet as a parameter to ConstraintLayout.
  2. Assign references created in the ConstraintSet to composables using the tag modifier.
@Composable
fun DecoupledConstraintLayout() {
    WithConstraints {
        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.

Custom layouts

Some composable functions emit a piece of UI when invoked, that is then added to a UI tree that gets rendered on the screen. Each UI element has one parent and potentially many children. Each element also has a location within its parent, specified as an (x, y) position, and a size, specified as a width and a height.

Elements are asked to define their own Constraints that should be satisfied. Constraints restrict the minimum and maximum width and height of an element. If an element has child elements, the parent may measure each of the children to help determine the parent's size. Once an element reports its own size, it has an opportunity to place its child elements relative to itself, as described in detail in Creating custom layouts.

Single-pass measurement is good for performance, allowing Compose to efficiently handle deep UI trees. If a layout element measured its child twice and that child measured one of its children twice and so on, a single attempt to lay out a whole UI would have to do a lot of work, making it hard to keep your app performing well. However, there are times when you really need additional information on top of what a single child measurement would tell you. There are approaches that can efficiently cope with a situation like that, which are discussed in Using the layout modifier.

Using the layout modifier

You can use the layout modifier to modify how a composable is measured and laid out. Layout is a lambda; its parameters include the composable you can measure, passed as measurable, and that composable's passed constraints, passed as constraints. Most custom layout modifiers follow this pattern:

fun Modifier.customLayoutModifier(...) =
    Modifier.layout { measurable, constraints ->
  ...
})

Let's display a Text on the screen and control the distance from the top to the baseline of the first line of text. To do that, use the layout modifier to manually place the composable on the screen. Here's the desired behavior where the Text top padding is set 24.dp:

Shows the difference between normal UI padding, which sets the space between elements, and text padding that sets the space from one baseline to the next

Here's the code to produce that spacing:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = Modifier.layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.toIntPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }
}

Here's what's going on in that code:

  1. In the measurable lambda parameter, you measure the Text by calling measurable.measure(constraints).
  2. You specify the size of the composable by calling the layout(width, height) method, which also gives a lambda used for placing the children. In this case, it's the height between the last baseline and added top padding.
  3. You position the children on the screen by calling placeable.placeRelative(x, y). If the children aren't placed, they won't be visible. The y position corresponds to the top padding - the position of the first baseline of the text. placeRelative automatically mirrors the position in right-to-left contexts.

To verify this works as expected, use this modifier on a Text:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

Multiple previews of text elements; one shows ordinary padding between elements, the other shows padding from one baseline to the next

Creating custom layouts

The layout modifier only changes one composable. To manually control multiple composables, use the Layout composable instead. This composable allows you to measure and lay out children manually. All higher-level layouts like Column and Row are built with the Layout composable.

Let's build a simple implementation of Column. Most custom layouts follow this pattern:

@Composable
fun MyOwnColumn(
    modifier: Modifier = Modifier,
    content: @Composable() () -> Unit
) {
    Layout(
        modifier = modifier,
        children = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
    }
}

Similarly to the layout modifier, measurables is the list of children that need to be measured and constraints are the constraints passed to the Layout. Following the same logic as before, MyOwnColumn can be implemented like this:

@Composable
fun MyOwnColumn(
    modifier: Modifier = Modifier,
    content: @Composable() () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

The child composables are constrained by the Layout constraints, and they're placed based on the yPosition of the previous composable.

Here's how that custom composable would be used:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyOwnColumn(modifier.padding(8.dp)) {
        Text("MyOwnColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

alt_text

Layout direction

Change the layout direction of a composable by using the LayoutDirection ambient.

If you're placing composables manually on the screen, the LayoutDirection is part of the LayoutScope of the layout modifier or Layout composable.

When using layoutDirection, place composables using place. Unlike the placeRelative method, place doesn't change based on the reading direction (left-to-right versus right-to-left).

Learn more

To learn more, try the Layouts in Jetpack Compose codelab.