Jetpack Compose Basics: Discovering the Fun of Boxes and Surfaces

Esther Carrelle Rangira
6 min readJan 27, 2024

--

Introduction

Hey there! Ever wondered how to make cool stuff with Jetpack Compose? Well, your girl got you covered! In this article, we’re going to explore two things called Boxes and Surfaces. They’re like the superhero tools for creating awesome-looking screens. We’ll break down what they do and show you how to use them with some easy examples. So, if you’re ready for some UI magic, let’s dive in together! 🚀✨

Using Boxes

The Box is like the FrameLayout’s counterpart in composing UIs. Just as the FrameLayout, it helps in placing elements relative to their parent’s edges and allows you to stack them up. This comes in handy when you want elements to show up in certain spots or when dealing with elements that need to overlap, like dialogs.

Here is an example:

@Composable
fun MyBox(
modifier: Modifier = Modifier,
contentModifier: Modifier = Modifier
) {
Box(modifier = modifier.fillMaxSize()) {
Text(
text = "first",
fontSize = 22.sp,
modifier = contentModifier.align(Alignment.TopStart)
)

Text(
text = "second",
fontSize = 22.sp,
modifier = contentModifier.align(Alignment.Center)
)

Text(
text = "third",
fontSize = 22.sp,
modifier = contentModifier.align(Alignment.BottomEnd)
)
}
}

Hold on to your coding cap! This function is like a cool DJ with two rockstar friends — meet the modifier and contentModifier! 🎧 By default, they’re chillin’ with Modifier, the empty modifier guru. Now, here’s the fun part — you can invite custom modifiers to the party, changing how the parent Box or each content piece struts its stuff. It’s like giving your UI a makeover!

And guess what? The fun doesn’t stop there! Each element can throw a modifier function call after another, adding more bling to the customization party. 🕺 Imagine passing in a custom modifier that throws a bit of padding or stylish flair to the parent modifier. It’s like fashion for your app, and you get to reuse those custom styles everywhere! Plus, the end component adds a splash of extra customization — it’s the app’s way of saying, “I’m unique, baby!”

And just when you thought it couldn’t get better, you can pull the same stunt for content-based modifiers. 🎉 Here, the Box() is the party venue with three text fields strutting their stuff. Using the align modifier, they’re like dance pros grooving in three different hotspots on the screen. Talk about a UI dance floor that knows how to move! 💃🕺

Let’s stop the party here and let’s get serious!

The text fields appear diagonally across the screen, with the first one at the top-left corner, the second one in the center and the last one at the bottom-right corner.

Using a Box is really useful in specific situations, and they make positioning elements incredibly easy.

Let’s explore Boxes

When you have multiple children inside a Box, they’re rendered in the same order as you placed them inside the Box. Here’s the implementation:

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

contentAlignment allows you to set the default Alignment to its children. If you want to have different Alignments between each child, you need to set Alignment by using Modifier.align() on a child.

propagateMinConstraints defines if 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.

You can set the Alignment to any edge of the screen as well as in relation to the center, using any of the following types of alignment:

  • TopStart
  • TopCenter
  • TopEnd
  • CenterStart
  • Center
  • CenterEnd
  • BottomStart
  • BottomCenter
  • BottomEnd

Where each of the alignments refers to which part of the screen the Box will attach an item to.

Surfaces

Surface is a new layout that serves as a central metaphor in Material Design. What’s unique about Surface is it can only hold one child at a time, but it provides many style treatments for the content of its children, such as the elevation, border and more.

@Composable
fun SurfaceScreen(modifier: Modifier = Modifier) {

Box(modifier = modifier.fillMaxSize()) {
MySurface(modifier = modifier.align(Alignment.Center))
}
}

@Composable
fun MySurface(modifier: Modifier) {
//TODO write your code here
}

To show all that the Surface can do, the example is set inside a full-screen Box() and an Alignment.Center. All that’s left is to implement the empty MySurface(). To do this, add the following code to finish it:

@Composable
fun MySurface(modifier: Modifier) {
Surface(
modifier = modifier.size(100.dp), // 1
color = Color.LightGray, // 2
contentColor = colorResource(id = R.color.colorPrimary), // 2
elevation = 1.dp, // 3
border = BorderStroke(1.dp, Color.Black) // 4
) {
MyColumn() // 5
}
}

There are many small steps in this code, so go over them one by one:

  1. You first set the size of the surface to 100dp in both height and width using Modifier.size().
  2. Then you set the color of the surface to Color.LightGray and the color of its content to colorPrimary. The surface will be gray, and Surface will set the contentColor to all the elements it applies to—such as Text elements.
  3. You add an elevation of 1dp to raise the Surface above other elements.
  4. You also add a black border to outline the Surface.
  5. Finally, you set the child to the Surface to be the MyColumn() you defined earlier.

This is a perfect example of the power of Jetpack Compose. You can reuse each of the screens and composable functions you implemented before. This time, you reused MyColumn(), with three vertical Text elements.

Build and run and select Surface from the navigation menu.

Let’s explore Surfaces

To see what else a Surface() has to offer, open its signature:

@Composable
fun Surface(
modifier: Modifier = Modifier,
shape: Shape = RectangleShape,
color: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(color),
border: BorderStroke? = null,
elevation: Dp = 0.dp,
content: @Composable () -> Unit
)

These parameters define Surface’s purpose. There are five purposes in total:

  • Shape: Clips the children with the defined shape.
  • Color: Fills the shape with a color you define.
  • Border: Draws borders, if they’re set.
  • Elevation: Sets the elevation and draws an appropriate shadow.
  • Content: Sets the default color for its content with the defined contentColor.

The most common way to use a Surface is as the root layout of your components. Since it can hold only one child, that child is usually another layout that positions the rest of the elements. The Surface() doesn’t handle positioning—its child does.

Note: There’s a popular custom Surface implementation called Card. A Card has exactly the same five purposes and can only hold one child. The only difference between the Card and a Surface are its default parameters. A Card has a predefined elevation and uses a material theme shape with rounded corners.

Conclusion

Mastering Boxes and Surfaces in Jetpack Compose opens up a world of possibilities for creating dynamic and visually appealing UIs. As you continue your journey with Jetpack Compose,. stay tuned for more insights into the evolving landscape of Jetpack Compose!

I appreciate you taking the time to read this. If you found it helpful, please consider giving it a thumbs-up and following me.

LinkedIn:

https://www.linkedin.com/in/esther-carrelle-rangira/

Github:

If you want to read more about this, here is a link to the official android documentation:

Here’s an additional link to a comprehensive course:

--

--