Jetpack Compose series:

Table of content:

  1. Introduction
  2. Alignment
  3. Arrangement
  4. Modifiers
    4.1. Set the content size with modifiers
    4.2. The "align" modifier
    4.3. The "padding" modifier
  5. Layouts
    5.1. The "Box" layout
    5.2. The "Column" layout
    5.3. The "Row" layout
    5.4. The "ConstraintLayout" layout
    5.5. The "Scaffold" layout
  6. Summary

Introduction

Jetpack Compose provides a declarative way of building the UI of the Android app. Layouts are an essential component for creating UI, and Jetpack Compose already supports a few of them.

This article will explore available layouts in Jetpack Compose combined with modifiers, alignments, and arrangements that help to build the UI of Android apps.

The content of this article is based on a 1.0.0-alpha11 version of Jetpack Compose.

Alignment

I would like to start with Alignment and Arrangement that help us with UI elements’ arrangements.

There are nine alignment options that can be applied to child UI elements:

TopStart TopCenter TopEnd
CenterStart Center CenterEnd
BottomStart BottomCenter BottomEnd

The alignment is used as one of the parameters for the "Box" layout. We will explore that in the "Layout" section.

Demo: Alignment as a parameter in Box layout.
Box(
    contentAlignment = Alignment.BottomEnd,
    modifier = Modifier.preferredSize(height = 120.dp, width = 200.dp)
        .background(Color(0xFFCA8DC4))
) {
    Box(
        modifier = Modifier.padding(8.dp)
            .preferredSize(80.dp)
            .background(Color(0xFF342E6C))
    ) {
        ...
    }
}

We also have a set of constants for Alignment.Horizontal and Alignment.Vertical interfaces.

Constants for the Alignment.Horizontal interface:

  • Start
  • CenterHorizontally
  • End

Constants for the Alignment.Vertical interface:

  • Top
  • CenterVertically
  • Bottom

Alignment.Horizontal and Alignment.Vertical are used as a parameter for the "Column and "Row" layouts. We will explore those in the "Layout" section.

Arrangement

Arrangement is used to specify the arrangement of child elements in "Column" and "Row" layouts in the axis direction (horizontal and vertical).
We have a set of predefined values for horizontal and vertical arrangements:

  • Horizontal
    • Start
    • End
    • Center
    • SpaceEvenly
    • SpaceBetween
    • SpaceAround
  • Vertical
    • Top
    • Bottom
    • Center
    • SpaceEvenly
    • SpaceBetween
    • SpaceAround

Let's take a look at a visual representation of basic arrangements like "Start", "End", "Center", "Top", and "Bottom".

Arrangements: Start, End, Center, Top, and Bottom.

We also have three arrangements that can be applied as vertical and horizontal arrangements:

  • SpaceEvenly
  • SpaceBetween
  • SpaceAround

The SpaceEvenly arrangement places child elements across the main axis, including free space before the first and after the last child.

Arrangement.SpaceEvenly.

The SpaceBetween arrangement places child elements across the main axis without free space before first and after the last child.

Arrangement.SpaceBetween.

The SpaceAround arrangement places child elements across the main axis with half of the free space before the first and after the last child.

Arrangement.SpaceAround.

Modifiers

If you are not familiar with a modifier, you can read more in the official documentation.

The modifier is an ordered, immutable collection of modifier elements that decorate or add behavior to Compose UI elements. For example, backgrounds, padding, and click event listeners decorate or add behavior to rows, text, or buttons.

All modifiers discussed are used for the layout-related examples.

Set the content size with modifiers

The dp (density-independent pixels) is used for almost all modifiers with sizes. The dp extension function can be applied to any Int, Float, or Double value.

// Int
val height = 100.dp
// Float
val height = 90.0f.dp
// Double
val height = 100.0.dp

We can explicitly set the height, width, and size to the UI element using one of the following modifiers:

  • preferredHeight(height: Dp) sets the UI element’s height to a preferred value.
  • preferredWidth(width: Dp) sets the UI element’s width to a preferred value.
  • preferredSize(size: Dp) sets the height and width of an element to a similar size value.
  • preferredSize(width: Dp, height: Dp) sets the height and with of an element.

We can combine multiple modifiers: modifierA().modifierB().

Let's use the preferredHeight, preferredWidth, and preferredSize modifiers in a few examples below.

Demo: preferredHeight, preferredWidth and preferredSize.
// Example: preferredHeight = 50.dp, preferredWidth = 200.dp
Box(
    modifier = Modifier.preferredHeight(50.dp)
        .preferredWidth(200.dp)
        .background(Color(0xFFCA8DC4))
) {
    ...
}

// Example: preferredHeight = 100.dp, preferredWidth = 100.dp
Box(
    modifier = Modifier.preferredHeight(100.dp)
        .preferredWidth(100.dp)
        .background(Color(0xFF342E6C))
) {

}

// Example: preferredSize = 100.dp
Box(
    modifier = Modifier.preferredSize(100.dp)
        .background(Color.Yellow)
) {

}

Sometimes, we want to use all the available space of a container. We can do this with one of the following modifiers:

  • fillMaxHeight() fills the height of an element with the maximum height of a parent.
  • fillMaxWidth() fills the width of an element with the maximum width of a parent.
  • fillMaxSize() fills the height and width of an element with the maximum height and width of a parent.
Demo: fillMaxWidth and preferredHeight.
Box(
    modifier = Modifier.preferredSize(height = 100.dp, width = 200.dp)
        .background(Color(0xFFCA8DC4))
) {
    Box(
        modifier = Modifier.preferredHeight(70.dp)
            .fillMaxWidth()
            .background(Color(0xFF342E6C))
    ) {
        ...
    }
}

The "align" modifier

The align modifiers allows us to set arrangement of children inside the Box Scope.

Demo: the "align" modifier.
Box(
    modifier = Modifier.preferredSize(height = 120.dp, width = 300.dp)
) {
    Text(text = "TopStart", modifier = Modifier.align(Alignment.TopStart))
    Text(text = "TopCenter", modifier = Modifier.align(Alignment.TopCenter))
    Text(text = "TopEnd", modifier = Modifier.align(Alignment.TopEnd))

    Text(text = "CenterStart", modifier = Modifier.align(Alignment.CenterStart))
    Text(text = "Center", modifier = Modifier.align(Alignment.Center))
    Text(text = "CenterEnd", modifier = Modifier.align(Alignment.CenterEnd))

    Text(text = "BottomStart", modifier = Modifier.align(Alignment.BottomStart))
    Text(text = "BottomCenter", modifier = Modifier.align(Alignment.BottomCenter))
    Text(text = "BottomEnd", modifier = Modifier.align(Alignment.BottomEnd))
}

The "padding" modifier

The padding modifier allows us to add space around an element. We have three different options for adding a padding around an element:

  • padding(start: Dp = 0.dp, top: Dp = 0.dp, end: Dp = 0.dp, bottom: Dp = 0.dp)
  • padding(horizontal: Dp = 0.dp, vertical: Dp = 0.dp)
  • padding(all: Dp)

As you can see, different modifiers allow us to write less boilerplate code when we need to set similar padding around the elements or set the different horizontal and vertical paddings.

Demo: padding(horizontal, vertical).
Box(
    modifier = Modifier.preferredSize(height = 120.dp, width = 200.dp)
        .background(Color(0xFFCA8DC4))
) {
    Box(
        modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
            .fillMaxSize()
            .background(Color(0xFF342E6C))
    ) {
        ...
    }
}

Layouts

Building the UI of a modern mobile application, we need to combine multiple UI elements, and multiple available layouts help us with that.

Jetpack Compose supports the following layouts:

  • Box
  • Column
  • Row
  • ConstraintLayout
  • Scaffold

The "Box" layout

The "Box" layout stacks every child on top of each other. By default, the children will be placed at the Alignment.TopStart position.

@Composable
inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit
)

Examples:

Example 1: the "Box" layout.
Box(
    modifier = Modifier.preferredSize(height = 120.dp, width = 300.dp)
) {
    Text(
        text = "Very important text", 
        fontSize = 20.sp
    )
    Text(
        text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", 
        fontSize = 20.sp
    )
}

We can use the align modifier to set alignment for children elements.

Example 2: the "Box" layout.
Box(
    modifier = Modifier.preferredSize(height = 120.dp, width = 300.dp)
) {
    Text(text = "TopStart", modifier = Modifier.align(Alignment.TopStart))
    Text(text = "Center", modifier = Modifier.align(Alignment.Center))
    Text(text = "BottomEnd", modifier = Modifier.align(Alignment.BottomEnd))
}

The "Column" layout

The "Column" layout stacks children in a vertical sequence. Comparing this with available layouts in Android development, the closest one is a LinearLayout with vertical orientation. All child items will be arranged vertically at Top and aligned horizontally at Start, by default.

@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) 

Examples:

Example 1: the "Column" layout.
Column(
    horizontalAlignment = Alignment.CenterHorizontally,
    modifier = Modifier.fillMaxWidth()
) {
    Image(
        bitmap = imageResource(R.drawable.espresso_small),
        contentDescription = "Espresso"
    )
    Text(
        text = "Espresso",
        fontSize = 50.sp
    )
}

We can add different weights to children inside the "Column" layout by using the weight(weight: Float) modifier. Let's take a look at an example of when different weight can be helpful:

Example 2: the "Column" layout.
Column(modifier = Modifier.preferredHeight(200.dp)){
    Text(
        text = "Very important text",
        fontSize = 20.sp,
        modifier = Modifier
            .fillMaxWidth()
            .weight(2f)
            .background(Color.White)
    )
    Text(
        text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        fontSize = 20.sp,
        modifier = Modifier
            .fillMaxWidth()
            .weight(1f)
            .background(Color.LightGray)
    )
}

The "Row" layout

The "Row" layout stacks children in a horizontal sequence. Comparing it with available layouts in Android development, the closest one is a LinearLayout with a horizontal orientation. All child items will be aligned vertically at Top and arranged horizontally at Start, by default.

@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: @Composable RowScope.() -> Unit
)

Examples:

Example 1: the "Row" layout.
Row(
    verticalAlignment = Alignment.CenterVertically,
    modifier = Modifier.fillMaxWidth()
) {
    Image(
        bitmap = imageResource(R.drawable.espresso_small),
        contentDescription = "Espresso",
        modifier = Modifier.preferredSize(100.dp)
    )
    Text(
        text = "Espresso",
        fontSize = 30.sp
    )
}

We can add different weights to children inside the "Column" layout using the weight(weight: Float) modifier. Let's take a look at an example of when different weight can be helpful:

Example 2: the "Row" layout.
Row(modifier = Modifier.preferredHeight(200.dp)){
    Text(
        text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        fontSize = 20.sp,
        modifier = Modifier
            .fillMaxWidth()
            .weight(3f)
            .background(Color.White)
    )
    Text(
        text = "Very important text",
        fontSize = 20.sp,
        modifier = Modifier
            .fillMaxWidth()
            .weight(1f)
            .background(Color.LightGray)
    )
}

Let's combine "Column" and "Row" layouts in one example.

Example: "Column" and "Row" layouts.
Row(
    verticalAlignment = Alignment.CenterVertically,
    modifier = Modifier.fillMaxWidth()
) {
    Image(
        bitmap = imageResource(R.drawable.espresso_small),
        contentDescription = "Espresso",
        modifier = Modifier.preferredSize(100.dp)
    )
    Column(
        modifier = Modifier.padding(horizontal = 8.dp)
    ) {
        Text(
            text = "Espresso",
            fontSize = 24.sp
        )
        Text(
            text = "Espresso is coffee of Italian origin, brewed by forcing a small amount of nearly boiling water under pressure (expressing) through finely-ground coffee beans.",
            style = TextStyle(textAlign = TextAlign.Justify)
        )
    }
}

The "ConstraintLayout" layout

The "ConstraintLayout" layout positions children according to the constraints between them. It's similar to the available "ConstraintLayout" for Android development.

@Composable
fun ConstraintLayout(
    modifier: Modifier = Modifier,
    content: @Composable ConstraintLayoutScope.() -> Unit
)

Let's try to rebuild the sample with a coffee card using "ConstraintLayout" instead of a combination of "Column" and "Row" layouts.

We can define constraints use the constrainAs modifier.

We can define constraints

Example: the "ConstraintLayout" layout.
ConstraintLayout(
    modifier = Modifier.fillMaxWidth()
) {
    val (logo, title, description) = createRefs()

    Image(
        bitmap = imageResource(R.drawable.espresso_small),
        contentDescription = "Espresso",
        modifier = Modifier.preferredSize(100.dp)
            .constrainAs(logo) {
                top.linkTo(parent.top)
            }
    )
    Text(
        text = "Espresso",
        fontSize = 24.sp,
        modifier = Modifier.constrainAs(title) {
            top.linkTo(parent.top)
            linkTo(start = logo.end, end = parent.end, startMargin = 8.dp, endMargin = 8.dp)
            width = Dimension.fillToConstraints
        }
    )
    Text(
        text = "Espresso is coffee of Italian origin, brewed by forcing a small amount of nearly boiling water under pressure (expressing) through finely-ground coffee beans.",
        style = TextStyle(textAlign = TextAlign.Justify),
        modifier = Modifier.constrainAs(description) {
            top.linkTo(title.bottom)
            linkTo(start = title.start, end = title.end)
            width = Dimension.fillToConstraints
        }
    )
}

The "Scaffold" layout

The "Scaffold" layout contains a basic implementation of the material design app structure with the following components:

  • TopBar
  • BottomBar
  • FloatingActionButton
  • Drawer
@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = emptyContent(),
    bottomBar: @Composable () -> Unit = emptyContent(),
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = emptyContent(),
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    bodyContent: @Composable (PaddingValues) -> Unit
)

Let's build a few simple layouts with the "Scaffold" layout:

  • Layout with "TopBar" and content area
  • Layout with "TopBar", "BottomBar", "FloatingActionButton" and content area

The layout with "TopBar" and content area

Example 1: the "Scaffold" layout.
Scaffold(
    topBar = {
        TopAppBar(title = { Text(text = "Title") })
    },
    bodyContent = {
        Text(
            text = "Content",
            fontSize = 40.sp
        )
    }
)

The layout with "TopBar", "BottomBar", "Drawer", "FloatingActionButton" and content area

Example 2: the "Scaffold" layout.
Scaffold(
    topBar = {
        TopAppBar(
            title = { Text(text = "Title") }
        )
    },
    bottomBar = {
        BottomAppBar {
            Text(
                text = "Item 1",
                textAlign = TextAlign.Center,
                modifier = Modifier.weight(1f)
            )
            Text(
                text = "Item 2",
                textAlign = TextAlign.Center,
                modifier = Modifier.weight(1f)
            )
            Text(
                text = "Item 3",
                textAlign = TextAlign.Center,
                modifier = Modifier.weight(1f)
            )
        }
    },
    bodyContent = {
        Text(
            text = "Content",
            fontSize = 40.sp
        )
    },
    floatingActionButtonPosition = FabPosition.End,
    floatingActionButton = {
        FloatingActionButton(onClick = { }) {
            Text(text = "+")
        }
    },
)

Summary

Jetpack Compose provides possibilities for building complex layouts for Android applications using modifiers, alignments, arrangements, and layouts.

We explored only modifiers for setting the size of UI elements:

  • The preferredHeight(height: Dp) sets an UI element’s height to the preferred value.
  • The preferredWidth(width: Dp) sets an UI element’s width to the preferred value.
  • The preferredSize(size: Dp) sets an element’s height and width to a similar size value.
  • The fillMaxHeight() fills the height of an element with the maximum height of a parent.
  • The fillMaxWidth() fills the width of an element with a parent’s maximum width.
  • The fillMaxSize() fills the height and width of an element with a parent’s maximum height and width.

Alignments and arrangements help us to position child elements inside the parent container.

The following layouts are available in Jetpack Compose:

  • The "Box" layout stacks every child on top of each other.
  • The "Column" layout stacks children in a vertical sequence.
  • The "Row" layout stacks children in a horizontal sequence.
  • The "ConstraintLayout" layout positions children according to the constraints between them.
  • The "Scaffold" layout contains a basic implementation of the material design app structure.