Jetpack Compose Basics: Discovering the Fun of Boxes and Surfaces
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:
- You first set the size of the surface to
100dp
in both height and width usingModifier.size()
. - Then you set the color of the surface to
Color.LightGray
and the color of its content tocolorPrimary
. The surface will be gray, andSurface
will set thecontentColor
to all the elements it applies to—such asText
elements. - You add an elevation of
1dp
to raise theSurface
above other elements. - You also add a black border to outline the
Surface
. - Finally, you set the child to the
Surface
to be theMyColumn()
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: