Instruction
Exploring Modifiers
Modifiers in Compose tell a UI element how it should layout, display, and behave within its parent’s layout. They’re often referred to as decorators of a composable, that let you:
- Change a composable’s size, layout, and behavior.
- Change its appearance.
- Add accessibility labels.
- Handle user inputs.
- Handle interactions like clicks, scrolling, dragging, zooming, etc.
Under the hood, modifiers are standard Kotlin functions. You use a modifier by calling one of the Modifier extension functions:
@Composable
fun HelloCompose() {
Text(
"Hello Compose!",
fontSize = 24.sp,
modifier = Modifier
.background(Color.Yellow)
)
}
In the example above, you created a Text composable that displays "Hello Compose!" on the screen and has a font size of 24sp.
This composable uses the background modifier to apply a yellow background to the text.
You can also chain together multiple modifiers to “compose” them, as shown below:
@Composable
fun HelloCompose() {
Text(
"Hello Compose!",
fontSize = 24.sp,
modifier = Modifier
.background(Color.Yellow)
.fillMaxWidth()
.border(2.dp, Color.Red)
)
}
In the snippet above, you chained on the fillMaxWidth modifier to make the text take the entire width of the screen and the border modifier to give the text a red border of 2dp.
Ordering Modifiers
When working with modifiers in a chain, one of the most important things to keep in mind is ordering. The order of the modifiers influences the look and behavior.
Here’s a classic example of modifier chaining:
@Composable
fun RedCircle() {
Box(
modifier = Modifier
.size(40.dp)
.background(Color.Red)
.clip(CircleShape)
)
}
In the example above, you created a composable named RedCircle, which uses a Box composable. Box is a layout composable that’s covered in a later chapter.
You’re then chaining three modifiers on the box to give it a size of 40 dp, a red background color, and a circular clip.
When you preview this composable, you’ll see something like this:
You might think this is a bug, but it’s the intended behavior.
When chaining modifiers, the order of each modifier in the chain matters. Each modifier in the chain modifies and prepares the composable for the next modifier in the chain.
Keeping this in mind, try to step through the modifier chain.
- You first defined the width and height of the composable with the
sizemodifier. - Since width and height are the same, you ended up with a square.
- You then applied a
backgroundmodifier on the square, which gave you a red square. - You then applied the
clipmodifier to clip the square to a circle shape.
In this sequence, the result of all previous modifiers was used by the modifier that followed it. But, in the case of the clip modifier, you applied the modifier, but nothing consumed the result, so the content remained the same.
If you chain a subsequent background modifier to the chain, you’ll notice the square does get clipped:
@Composable
fun RedCircle() {
Box(
modifier = Modifier
.size(40.dp)
.background(Color.Red)
.clip(CircleShape)
.background(Color.White)
)
}
You can now see the effect of the clip modifier. It clipped the shape to a circle, which was then used by the ̀background modifier.
With this behavior in mind, you can now fix the RedCircle composable by reordering the modifiers as follows:
@Composable
fun RedCircle() {
Box(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(Color.Red)
)
}
With this ordering, your composable will render as shown below:
Built-in Modifiers
Jetpack Compose offers a list of built-in modifiers that help you decorate your composables. You can find a full list of these compose modifiers on the Android Developers documentation here.
Some of the most used ones that you’ll run into are the following:
Sizing
By default, layout composables wrap their children. But you can set a size using the size modifier to override this behavior:
@Composable
fun Card() {
Box(
modifier = Modifier
.size(width = 400.dp, height = 200.dp)
)
/*...*/
}
In the example above, the card will use a fixed width of 400dp and height of 200dp
Note: The size might not be respected if it doesn’t satisfy the constraints coming from the parent layout.
If you simply want your composable to span the entire available width, you can use fillMaxWidth.
Compose also offers fillMaxHeight, where your composable can span the entire height, and fillMaxSize where your composable can take up all the available space.
Padding
To add padding around an element, you can use the padding modifier:
@Composable
fun HelloCompose() {
Text(
"Hello Compose!",
fontSize = 24.sp,
modifier = Modifier
.padding(16.dp)
.background(Color.Yellow)
.fillMaxWidth()
.border(2.dp, Color.Red)
)
}
The example above applies a padding of 16dp in all directions and is rendered as follows:
The padding modifier has several overrides. You can apply the padding to all four sides individually as follows:
@Composable
fun HelloCompose() {
Text(
"Hello Compose!",
fontSize = 24.sp,
modifier = Modifier
.padding(top = 32.dp, start = 24.dp, end = 2.dp, bottom = 4.dp)
.background(Color.Yellow)
.fillMaxWidth()
.border(2.dp, Color.Red)
)
}
Or apply padding in the horizontal and vertical directions as follows:
@Composable
fun HelloCompose() {
Text(
"Hello Compose!",
fontSize = 24.sp,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.background(Color.Yellow)
.fillMaxWidth()
.border(2.dp, Color.Red)
)
}
Clickable
To enable click input in your composables, you can use the clickable modifier.
The signature of the clickable modifier is as follows:
Modifier.clickable(
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit
)
Here’s what each parameter signifies:
-
enabled: Controls the enabled state. When set to false, it will appear disabled for accessibility services. -
onClickLabel: Semantic label for accessibility. -
role: The type of user interface element. Accessibility services use this to describe the element. -
onClick: The lambda to be called when the user clicks the element.
Here’s an example usage of clickable modifier:
@Composable
fun RedCircle() {
Box(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(Color.Red)
.clickable {
println("Red circle clicked!!")
}
)
}
Background
You use the background modifier to draw a shape with a solid color behind it.
The signature for background is as follows:
Modifier.background(
color: Color,
shape: Shape = RectangleShape
)
It’s a best practice to have all of your composables accept a modifier parameter and pass that modifier to its first child that emits UI. Doing so makes your code more reusable and makes its behavior more predictable and intuitive.
You can read more about it here