Modifiers are the cornerstone to building custom UIs when using Jetpack Compose. Modifiers are objects that only affect the behavior and appearance without altering the internal state.
Modifiers allows to customize multiple properties such as size, paddings, alignment, background color, click actions, and more. Modifiers are immutable and can be integrated or chained to add multiple behaviors to a single composable.
Modifiers precisely encapsulate the idea of composition over inheritance by providing several interfaces and implementations to link logic and behavior to layouts.
Let’s see the basic structure of a Modifier in a composable example:
@Composable
fun TitleExample(){
Text(
text = "Hello, Folks!",
modifier = Modifier.padding(12.dp)
)
}
In the previous code, the Modifier adds padding around the Text composable.
Considering the wide range of options offered by the Modifiers, Let’s dive into them based on their functionality while looking at some code examples to ease their understanding.
Layout modifiers influence how the composable is positioned within its parent layout. These modifiers allow us to manage the size, position, and alignment of a composable within the available space.
The padding modifier adds padding inside the composable, making space between the composable’s content and its bounds.
@Composable
fun PaddingModifierExample() {
Box(
modifier = Modifier
.padding(24.dp)
.size(80.dp)
.background(Color.Gray)
) {
Text(
text = "Hello There!",
color = Color.Black,
modifier = Modifier.background(Color.White)
)
}
}
The padding(24.dp) modifier adds 24 dp of padding inside the Box, pushing the content (Text) away from the Box frontier, creating an inner margin.
Tips
- By default, the
paddingModifier applies the set value in all directions of the composable, if you want to adjust the padding just in a specific direction, specify the parameters start, top, end, or bottom instead of passing a single value.- Padding is a layout modifier, therefore, it should be applied before size and drawing modifiers.
- The Scaffold composable provides padding values to define the padding that will be applied along the inside edges of a box. The value will only be provided if the scaffold view’s bottomBar is set. Otherwise, it will only return 0.0 dp.
The fillMaxSize modifier makes the composable fill the maximum space available in its parent.
@Composable
fun FillMaxSizeModifierExample() {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Gray)
) {
Text(
text = "Filling",
color = Color.Black,
modifier = Modifier.background(Color.White).align(Alignment.TopCenter)
)
}
}
Tips
- Applying the fillMaxSize modifier, the composable occupies the entire available space, both vertically and horizontally.
- It’s frequently used in containers that need to fill the screen, such as background layers, overlays, or full-screen dialogs.
The wrapContentSize modifier grants the ability to wrap the composable around its content to adapt seamlessly to the intrinsic size of its content.
@Composable
fun WrapContentSizeModifierExample() {
Box(
modifier = Modifier
.size(150.dp)
.background(Color.Gray)
) {
Box(
modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.BottomCenter)
.size(50.dp)
.background(Color.Blue)
)
}
}
Tips
- The wrapContentSize modifier adjusts the composables size to fit its content, aligning it as specified (e.g., BottomCenter). By default, the content is centered.
- This modifier should be placed before size modifiers to ensure the correct wrapping behavior.
The size modifier places a fixed size for the composable, overriding any default or inherited parent sizing.
@Composable
fun SizeModifierExample() {
Box(
modifier = Modifier
.size(80.dp)
.background(Color.Yellow)
) {
Text(
text = "Fire 💥",
color = Color.Black,
modifier = Modifier.align(Alignment.Center)
)
}
}
Tips
- The size modifier sets the dimensions of the composable, making it exactly 80 dp by 80 dp.
- If the design needs to consider modifiers like padding or alignment, make sure to apply them before setting the size modifier to ensure they affect the composable appropriately.
The offset modifier moves the composable’s position by a specified amount in the X and Y positions without affecting its nearby layout.
@Composable
fun OffsetModifierExample(
) {
Box(
modifier = Modifier
.padding(25.dp)
.background(Color.Cyan)
.height(100.dp),
contentAlignment = Alignment.TopCenter
) {
Text(
text = "Hey there!",
modifier = Modifier.offset(x = 0.dp, y = -(15.dp)),
style = TextStyle(
color = Color.Black,
fontSize = 20.sp
)
)
}
}
Tips
- The offset(x = 0.dp, y = - (15.dp)) shifts the inner Text composable without modifying its size or affecting the overall layout flow.
- If the design demands the use of custom size or draw modifiers, the offset modifier should be used before applying them for precise positioning.
The align modifier describes the alignment of a composable within its parent layout.
@Composable
fun AlignModifierExample() {
Box(
modifier = Modifier
.size(88.dp)
.background(Color.Magenta)
) {
Text(
text = "Woops!",
color = Color.Black,
modifier = Modifier
.align(Alignment.CenterEnd)
.background(Color.Cyan)
)
}
}
Tips
- It’s decisive to use align initially in the modifier chain for the correct layout effect.
The weight modifier is limited to use on the Row layouts, distributing space among children’s components based on their weight.
@Composable
fun WeightModifierExample() {
Row(
modifier = Modifier
.fillMaxWidth()
.height(25.dp)
) {
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.background(Color.White)
)
Box(
modifier = Modifier
.weight(0.5f)
.fillMaxHeight()
.background(Color.LightGray)
)
Box(
modifier = Modifier
.weight(2f)
.fillMaxHeight()
.background(Color.DarkGray)
)
Box(
modifier = Modifier
.weight(0.3f)
.fillMaxHeight()
.background(Color.Black)
)
}
}
Tips
- The children’s components share the available space correspondingly based on their weight values.
- Set the weight modifier before any draw modifier, like the border, to prevent incorrect layout calculations.
Drawing modifiers determine how a composable is displayed on the screen. They define the visual appearance, such as background, borders, shadows, and clipping.
The background modifier appends a background color or shape to a composable.
@Composable
fun BackgroundModifierExample() {
Box(
modifier = Modifier
.size(88.dp)
.background(color = Color.Cyan, shape = CircleShape)
) {
Text(
text = "Snaps!",
color = Color.Black,
modifier = Modifier.fillMaxSize()
.wrapContentSize()
.background(Color.Blue)
)
}
}
Tips
- The background color and shape could be set at the same time.
- By default, the shape used is the RectangleShape.
- If you need to apply modifiers like padding, set it before using the background modifier to make sure the background covers the desired area.
The border modifier adjoins a border throughout a composable.
@Composable
fun BorderModifierExample() {
Box(
modifier = Modifier
.padding(8.dp)
.border(
width = 5.dp,
color = Color.Magenta,
shape = RoundedCornerShape(8.dp)
)
.size(87.dp)
)
}
Tips
- You can define the width, color, and shape of the border modifier. By default, the shape is RectangleShape.
- Layout modifiers should be put before the border to ensure correct spacing and border positioning.
The clip modifier attaches the composable to a specified shape.
@Composable
fun ClipModifierExample() {
Box(
modifier = Modifier
.size(60.dp)
.background(Color.DarkGray)
.clip(RoundedCornerShape(10.dp))
.border(4.dp, Color.Magenta, RoundedCornerShape(10.dp))
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = null,
modifier = Modifier
.size(100.dp)
.clip(RoundedCornerShape(10.dp))
.border(2.dp, Color.White, RoundedCornerShape(10.dp))
.background(Color.LightGray)
)
}
}
Tips
- The clip modifier impounds the composable within the defined shape, such as RoundedCornerShape, cutting off any content outside the boundary.
- Some clip options must set the size in Dp to apply to the corners. (e.g. RoundedCornerShape, CutCornerShape).
The alpha modifier changes the transparency of a composable.
@Composable
fun AlphaModifierExample() {
Box(
modifier = Modifier
.alpha(0.3f)
.size(77.dp)
.background(Color.Black) // Applying the opacity to 30%, the black seems like a gray
)
}
Tips
- The alpha modifier decreases the composable’s opacity, making the background or overlapping elements partially visible.
- If the design demands layout and size tweaking, apply them before setting the alpha modifier for the correct visual effect.
The shadow modifier annexes a shadow effect to a composable.
@Composable
fun ShadowModifierExample() {
Box(
modifier = Modifier
.shadow(
elevation = 16.dp,
shape = CircleShape,
spotColor = Color.Green
)
.size(100.dp)
.background(Color.Yellow)
)
}
Tips
- The shadow modifier must be adjusted after layout modifiers to mirror the final composable size.
Interaction modifiers manage user inputs like clicks, scrolls, and gestures. They describe how the user actions affect composable behavior.
The clickable modifier allows a composable respond to click actions.
@Composable
fun ClickableModifierExample() {
Box(
modifier = Modifier
.size(88.dp)
.background(color = Color.Green, shape = CircleShape)
) {
Text(
text = "Hurry Up!",
color = Color.Black,
modifier = Modifier
.fillMaxSize()
.wrapContentSize().clickable {
// Handle click event here!
}
.background(Color.Magenta)
)
}
}
Tips
- Besides handling the user action, we can adapt the clickable modifier enabling or not depending on the requirements (by default is enabled), and for accessibility purposes, we can use onClickLabel and role parameters.
- The clickable modifier changes any composable into an interactive component, allowing a click reaction without requiring a dedicated button.
- It’s highly recommended to adjust layout modifiers first to ensure the clickable area is accurately set.
The toggleable modifier permits toggle behavior, which is useful for creating switch-like components.
@Composable
fun ToggleableModifierExample() {
var isEnabled by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.toggleable(
value = isEnabled,
onValueChange = { isEnabled = it }
)
.size(77.dp)
.background(if (isEnabled) Color.Green else Color.Red)
)
}
Tips
- The toggleable modifier attaches a toggle behavior, switching states when interacted with, similar to a switch.
- Try to set the layout modifiers before setting the toggleable modifier to ensure the interactive area is properly defined.
The draggable modifier grants a dragging action on the composable.
@Composable
fun DraggableModifierExample() {
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
Column {
Box(
modifier = Modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.size(86.dp)
.background(Color.Cyan)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
offsetX += dragAmount.x
offsetY += dragAmount.y
}
}
)
}
}
pointerInput modifier detects the whole dragging gesture and updates the composable position considering the user’s movement.
Tips
- This modifier only detects the gesture. It is necessary to hold the state and represent it on screen, like the previous example.
The scrollable modifier gives the scrollable ability vertically or horizontally to a composable within its parent.
@Composable
private fun ScrollableModifierExample() {
var offset by remember { mutableStateOf(0f) }
Column(
modifier = Modifier
.background(Color.LightGray)
.size(100.dp)
.scrollable(
orientation = Orientation.Vertical,
state = rememberScrollableState { delta -> offset += delta; delta }
)
) {
repeat(10) {
Text("Item $it", modifier = Modifier.padding(2.dp))
}
}
}
Tips
- The scrollable modifier turns a composable into a scrollable region, allowing smooth navigation within the content.
- For Column layouts, use the verticalScroll modifier, and for the Row layouts, the horizontalScroll modifier. It’s not necessary to translate or offset the contents; it just needs to set the ScrollState (to create it by default, use rememberScrollState()), allowing the change of the scroll position or get its current state.
The animation modifiers provide a declarative way to add fluid motion to UI elements by animating properties such as size, position, opacity, and other visual attributes.
The animateContentSize modifier animates a composable’s size change.
@Composable
fun AnimateContentSizeModifierExample() {
var expanded by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.background(Color.Magenta)
.animateContentSize()
.height(if (expanded) 400.dp else 50.dp)
.fillMaxWidth()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) { expanded = !expanded }
) {
Text(
text = if (expanded) "Caught you! Do you want to learn more? Go to https://wawand.co/" else "Read More",
modifier = Modifier.padding(8.dp)
)
}
}
Tips
- The animateContentSize modifier animates the composable’s size changes, adding smooth transitions through expand/collapse actions.
- The animation modifiers should be tied to layout tweaks to ensure the animation reflects the correct size adjustments.
The graphicsLayer modifier makes the composable’s content draw into a drawing layer, allowing us to perform transformations such as scaling, rotation, and translation.
@Composable
fun GraphicsLayerModifierExample() {
var rotationAngle by remember { mutableFloatStateOf(0f) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
Box(
modifier = Modifier
.graphicsLayer(
rotationZ = rotationAngle, scaleX = 1.5f, scaleY = 1.5f
)
.size(60.dp)
.background(Color.DarkGray)
.clip(RoundedCornerShape(10.dp))
.border(4.dp, Color.Magenta, RoundedCornerShape(10.dp))
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = null,
modifier = Modifier
.size(100.dp)
.clip(RoundedCornerShape(10.dp))
.border(2.dp, Color.White, RoundedCornerShape(10.dp))
.background(Color.LightGray)
)
}
Text(
modifier = Modifier.padding(vertical = 24.dp), text = "Rotate The Image 👇🏼"
)
Row(
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround
) {
Button(
onClick = { rotationAngle = 45f },
) {
Text("45°")
}
Button(
onClick = { rotationAngle = 90f },
) {
Text("90°")
}
Button(
onClick = { rotationAngle = 270f },
) {
Text("270°")
}
}
}
}
Tips
- The padding (layout modifier) should be placed before size and background to ensure precise visual behavior.
- The appropriate order provides for proper animation of position changes within the layout.
By understanding and effectively implementing these modifiers, you can create more engaging, responsive, and visually appealing applications while maintaining clean, maintainable code. The composable nature of modifiers, combined with their extensive functionality, makes them an indispensable tool in modern Android development.
It’s highly recommended to apply the modifiers in the right order to guarantee the visual behavior and the expected designs. Just keep in mind this order to set the composable’s modifiers.