Computer scienceMobileJetpack ComposeComposablesFoundation

Spacer and Padding

19 minutes read

Effective space management between UI elements is crucial in UI design. It helps your app look good and be easy to use. Jetpack Compose gives you easy yet strong tools for adding space and arranging layouts. In this topic, you will learn how to use these tools to make neat and attractive layouts. You will also see how to use them to improve the design of a simple profile card.

Starting design and final design of the profile card.

All previews of the profile card are shown in landscape orientation throughout this topic.

Spacer

You might be familiar with the "box model" from the Android View system, where each view is a box that can have an outer space called "margin" and an inner space called "padding". Jetpack Compose simplifies many things, including the box model.

Jetpack Compose offers a more flexible way to space elements with Spacer and padding. This choice fits with Compose's aim to have a more declarative UI, letting developers create custom spacing for their layouts.

The Spacer composable is an invisible component that creates space between UI elements. You can set a specific size to it, acting like what you would usually call a margin.

  • Using Spacer:

Here's the starting code for our profile card:

@Composable
fun ProfileCard() {
    Column(modifier = Modifier.fillMaxWidth()) {
        Row {
            Image(
                painter = painterResource(id = R.drawable.profile_picture),
                contentDescription = "Profile picture"
            )
            Column {
                // Name
                Text("Jane Doe", fontWeight = FontWeight.Bold)

                // Job
                Text("Android Developer")
            }
        }

        // Description
        Text("Passionate about creating seamless user experiences with Jetpack Compose.")

        // Contact details
        Text("Contact: [email protected]")
    }
}

To enhance the visual appeal of our ProfileCard and avoid a cluttered appearance, we can introduce a Spacer composable. This will create intentional space between UI elements, leading to a more aesthetically pleasing and user-friendly interface.

Here's how to incorporate a Spacer into the ProfileCard:

@Composable
fun ProfileCard() {
    Column(modifier = Modifier.fillMaxWidth()){
        Row {
            Image(/*...*/)

            // Space between the Image and Column
            Spacer(modifier = Modifier.width(16.dp))

            Column {/*...*/}
        }
        /*...*/
    }
}

Here, a Spacer with a width of 16.dp (density-independent pixels) creates horizontal space between elements in a Row.

You can use a Spacer in a Column by using the height modifier like so:

@Composable
fun ProfileCard() {
    Column(modifier = Modifier.fillMaxWidth()){
        Row {/*...*/}

        // Spacer to split the Row from the description Text
        Spacer(modifier = Modifier.height(16.dp))

        Text("Passionate about creating seamless user experiences with Jetpack Compose.")
        Text("Contact: [email protected]")
    }
}

With the incorporation of these Spacers, our ProfileCard now has a more polished and organized look, providing clear visual separation between UI elements.

Preview of `ProfileCard` with horizontal and vertical `Spacer`s applied.

Spacer flexibility

In Jetpack Compose, Spacer is not just a static space inserter; it's a flexible tool that can adapt to the available space within a layout. This adaptability is especially useful when you want to create designs that look good on different screen sizes or when you want to push content to one side or center it within a parent layout.

  • weight modifier

The weight modifier can be used with a Spacer to divide up the available space in a Row or Column. When you use weight, you are asking Compose to give a part of the available space to the Spacer, depending on its weight compared to other elements with weights.

Let's add a flexible Spacer to our ProfileCard:

@Composable
fun ProfileCard() {
    Column(modifier = Modifier.fillMaxWidth()){
        Row {/*...*/}

        /*...*/

        Text("Passionate about creating seamless user experiences with Jetpack Compose.")

        // Flexible spacer that will take up all available space
        // and push the contact details to the bottom
        Spacer(modifier = Modifier.weight(1f))

        Text("Contact: [email protected]")
    }
}

Here, the Spacer will use all the available space between the description and contact details Text elements, pushing the latter to the bottom.

Preview of `ProfileCard` with a flexible `Spacer` applied.

  • Flexible space distribution

You can place multiple Spacer composables with different weights to control how the space is divided.

For instance:

@Composable
fun FlexibleSpaceDistributionExample() {
    Row(modifier = Modifier.fillMaxWidth()) {
        Text("Left")
        Spacer(modifier = Modifier.weight(2f)) // Takes twice as much space as the right spacer
        Text("Center")
        Spacer(modifier = Modifier.weight(1f)) // Uses available space
        Text("Right")
    }
}

Preview of flexible space distribution.

Here, the space to the left of "Center" is twice that to the right, resulting in an asymmetric layout.

  • Dynamic Spacer size

Sometimes you might want a Spacer with a size that changes based on content or limits. You can do this by mixing the weight modifier with minimum size limits.

@Composable
fun DynamicSpacerSizeExample() {
    Column(modifier = Modifier.fillMaxHeight()) {
        Text("Top")
        Spacer(modifier = Modifier.weight(1f).heightIn(min = 50.dp)) // At least 50.dp tall
        Text("Bottom")
    }
}

In this case, the Spacer will stretch to fill the space but will not be less than 50.dp tall, making sure "Top" and "Bottom" are not too close to each other.

Padding

While Spacer adds space between elements, padding is used to add space inside the boundaries of a composable. You can apply padding to any composable using the padding modifier.

  • All sides padding:

You can apply padding in different ways. One way is to add the same padding to all sides of a composable by giving a single value to the padding modifier.

Let's make our ProfileCard look better by applying padding to mimic a shadow effect:

@Composable
fun ProfileCard() {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(all = 16.dp)
            .background(Color(0xFFEEEEEE)) // Color of the shadow effect
            .padding(all = 16.dp) // Additional padding for the shadow effect
            .background(Color.White)
            .padding(all = 16.dp)
    ) {
        /*...*/
    }        
}

This adds three layers of padding—16.dp each—to all sides of the Column, centering the content with equal space around it.

Preview of `ProfileCard` with `padding` applied around all content.

  • Padding in different directions

Instead of the same padding on all sides, you can specify it for individual sides: top, bottom, start, or end.

Let's put 8.dp padding on the top side of the name Text in our ProfileCard:

@Composable
fun ProfileCard() {
    Column(
        /*...*/
    ) {
        Row {
            /*...*/

            Column {
                Text(
                    text = "Jane Doe",
                    fontWeight = FontWeight.Bold,
                    modifier = Modifier.padding(top = 8.dp)
                )
                Text("Android Developer")
            }
        }

        /*...*/   

    }        
}
  • Absolute padding

The absolutePadding modifier lets you specify padding as left and right rather than start and end. In a left-to-right (LTR) layout, start means left, and end means right. But in a right-to-left (RTL) language like Arabic or Hebrew, start is right, and end is left. The absolutePadding ignores this and applies padding to the actual sides of the screen.

Since our ProfileCard will only show in an LTR language, let's add 16.dp absolutePadding to the left side of the description and contact details Texts:

@Composable
fun ProfileCard() {
    Column(
        /*...*/
    ) {
        Row { /*...*/ }

        /*...*/

        Text(
            text = "Passionate about creating seamless user experiences with Jetpack Compose.",
            modifier = Modifier.absolutePadding(left = 16.dp)
        )

        /*...*/

        Text(
            text = "Contact: [email protected]",
            modifier = Modifier.absolutePadding(left = 16.dp)
        )

    }        
}

Use absolutePadding carefully, since it can create a strange feeling for RTL users. Think about the overall app and whether keeping the padding's direction makes for a better experience.

Have you noticed that our ProfileCard looks just how we wanted now?

Preview of the final form of `ProfileCard`.

While our Profile card is ready, there's more to learn about padding in the following sections.

Horizontal and vertical padding

Sometimes you want to create a vertical or horizontal symmetry by applying the same padding to opposite sides of a composable. In this case, you have the horizontal parameter to apply the same padding to the start and end sides, and the vertical parameter to apply the same padding to the top and bottom sides.

For instance:

@Composable
fun SymmetricPaddingExample() {
    Column {
        // Horizontal padding
        Text(
            text = "This text has horizontal padding",
            modifier = Modifier
                .background(Color.Cyan)
                .padding(horizontal = 32.dp)
        )
        
        // Spacer for visual separation
        Spacer(modifier = Modifier.height(16.dp))

        // Vertical padding
        Text(
            text = "This text has vertical padding",
            modifier = Modifier
                .background(Color.Yellow)
                .padding(vertical = 24.dp)
        )
    }
}

In this example, the first Text composable has horizontal padding, which means it will have padding on the start and end sides, but not on the top and bottom. The second Text composable has vertical padding, which means it will have padding on the top and bottom, but not on the start and end sides.

Preview of horizontal and vertical padding.

Padding from baseline

Positioning text relative to its baseline is a common typographic practice. The paddingFromBaseline modifier is a specialized form of padding that's very useful when dealing with text elements. This modifier lets you adjust the space from the text baseline, which is the line most letters sit on and under which descenders hang.

paddingFromBaseline takes values in dp or sp (scaled pixels) units. sp is the better unit for Text components because it respects the user's font size preferences and accessibility settings.

Here's how you can use paddingFromBaseline:

@Composable
fun TextWithPaddingFromBaseline() {
    Column {
        Text(
            text = "Headline",
            style = MaterialTheme.typography.headlineLarge,
            modifier = Modifier.paddingFromBaseline(top = 40.sp)
        )
        Text(
            text = "Subhead",
            style = MaterialTheme.typography.headlineSmall,
            modifier = Modifier.paddingFromBaseline(top = 32.sp, bottom = 24.sp)
        )
    }
}

In this example, the paddingFromBaseline modifier ensures there's a 40.sp space from the "Headline" Text baseline and a 32.sp space from the "Subhead" Text baseline. It also adds a 24.sp space from the baseline to the bottom of the "Subhead" Text, making room for the next element.

Preview of applying `paddingFromBaseline` to `Text`.

Using paddingFromBaseline is great for keeping a consistent vertical rhythm in your typography because it respects the user's font and display settings, which is key for accessibility. Remember, paddingFromBaseline will look different depending on the font and size, as these affect the baseline position. So, when you use this modifier, think about your design's specific text needs and test it with different text settings to make sure it works right.

The "offset" modifier

Sometimes you may need negative padding to place a component exactly where you want, but if you try something like this: Modifier.padding(start = (-4).dp), you will get an error saying: "Padding must be non-negative".

To deal with this issue, Jetpack Compose offers the offset modifier. This modifier allows you to move a composable by shifting it from its original spot by a specific x (horizontal) and y (vertical) distance. This can be especially helpful for creating overlapping elements, adjusting the position of a UI component carefully, or adding motion effects.

The offset modifier is applied to a composable and takes two parameters: x and y, which represent the horizontal and vertical distance to shift the composable. Unlike the parameters of the padding modifier, the x and y parameters can take negative values.

Here's a simple example of using the offset modifier:

@Composable
fun ExampleWithOffset() {
    Row {
        repeat(20){
            Text("=",
                fontSize = 40.sp,
                modifier = Modifier.offset(x = (-it*8).dp))
        }
    }
}

In this example, we repeat the Text composable 20 times. Each time we offset the text by (-8).dp multiplied by the iteration number. This lets us create what looks like two parallel lines by repeating the equal sign (=) character.

Preview of creating two parallel lines using the `offset` modifier.

It's important to remember that using offset changes the visual position of a composable but does not change the layout space it takes up (That's why a white space is left at the end in our previous example's preview). The space reserved for the composable before the offset stays the same, and the composable may cover other elements due to the offset.

While the offset modifier is a powerful positioning tool, use it carefully. Overusing it can result in layouts that don't adapt well to different screen sizes and orientations. Also, when making applications accessible, think about how moving elements around might impact the experience for people using assistive technologies. For instance, screen readers navigate the UI based on the logical structure, not the visual layout, so an offset element might be read out of order from its visual placement, which could confuse users.

Best practices

When designing user interfaces with Jetpack Compose, using Spacer and padding effectively can greatly improve the usability and look of your app. Here are some best practices to keep in mind when using these layout tools:

  • Use padding for intrinsic spacing: You should use padding to create space within the boundaries of a composable element. If you want content within a composable to have a certain amount of space around it, apply padding directly to that composable.

  • Prefer Spacer for extra spacing: Use Spacer to create space between sibling composables in a layout, like between items in a Column or Row. Spacer is a specific composable that makes it clear the space is outside the elements it separates.

  • Be consistent: Keep your spacing consistent throughout your app. Choose a consistent set of spacing values, such as multiples of 4.dp or 8.dp, to create a balanced interface. Consistent spacing helps to keep a rhythm in your design.

  • Avoid too much Spacer: Although Spacer is useful, using it too much can fill your composable functions with unnecessary parts. If you are using many Spacers, think about using padding or changing your layout approach.

  • Use modifier chains smartly: You can chain modifiers together in Jetpack Compose. When you combine padding with other modifiers, the order can change the layout. For example, if you apply a background modifier before padding, the padding area will have the background color; if you apply it after, it will not.

  • Think about accessibility: Think about how your spacing can affect accessibility. Good spacing can make UI elements easier to use, especially for people with motor impairments who may find it hard to tap small touch targets.

  • Responsive design: Make sure your layout with padding and Spacers works well on different screen sizes and positions. Test your layouts in different conditions to ensure they scale properly and keep the design you intended.

  • Avoid hardcoded numbers: Instead of writing padding and Spacer values straight into your code, set them as constants or in a theme. This makes global adjustments easier and your code more tidy.

  • Look into performance: While it's not likely to cause big performance issues, be aware that too many composables for spacing can make the view hierarchy deeper. Always check your app to make sure that your layouts are not reducing performance.

Conclusion

Understanding and using Spacer and padding in Jetpack Compose is key for making well-designed and easy-to-use interfaces. Spacers are great for creating space between composables; padding defines the space inside a composable's boundaries. These tools help your design adjust to different screen sizes and directions, giving everyone a smooth experience.

You learned how Spacers can adjust to the screen with the weight modifier, which shares out space between elements. With padding, you can add the same amount of space on all sides, only on specific sides, or on opposite sides the same way, like top and bottom or left and right. For aligning text well, you can use the paddingFromBaseline modifier. And if you need to, use the offset modifier to move composables without changing where they sit in the layout.

Here is the final code for the ProfileCard:

@Composable
fun ProfileCard() {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(all = 16.dp)
            .background(Color(0xFFEEEEEE))
            .padding(all = 16.dp)
            .background(Color.White)
            .padding(all = 16.dp)
    ) {
        Row {
            Image(
                painter = painterResource(id = R.drawable.profile_picture),
                contentDescription = "Profile picture"
            )

            Spacer(modifier = Modifier.width(16.dp))

            Column {
                Text(
                    text = "Jane Doe",
                    fontWeight = FontWeight.Bold,
                    modifier = Modifier.padding(top = 8.dp)
                )
                Text("Android Developer")
            }
        }

        Spacer(modifier = Modifier.height(16.dp))

        Text(
            text = "Passionate about creating seamless user experiences with Jetpack Compose.",
            modifier = Modifier.absolutePadding(left = 16.dp)
        )

        Spacer(modifier = Modifier.weight(1f))

        Text(
            text = "Contact: [email protected]",
            modifier = Modifier.absolutePadding(left = 16.dp)
        )
    }
}
8 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo