Spacing Concepts in Jetpack Compose - An Overview
When learning to create a UI in Jetpack Compose, it can become overwhelming to decide how best to create visual spacing in a UI. Spacing can be applied using a Spacer
composable or the padding
modifier or even using Arrangement
. These can all create the same end effect, so how can one choose the "correct" solution? This article will lay out a mental model for choosing the right approach for fixed spacing situations that make the intent clear while improving long term maintainability.
Creating a Mental Model of Space in a UI #
When thinking of spacing it is important to consider the relationship between the components. The first relationship components can be in a "Parent-Child" relationship, i.e. one component is nested inside of the other. The other relationship is a "Sibling" relationship, i.e. the components are nested in the same wrapping (Parent) component. Now it is possible to outline approaches for each scenario.
Parent-Child Spacing #
In a Parent-Child relationship the most important consideration is who "owns" the spacing. It is best practice to have the parent own the spacing and not the child. This means that if there should be space between the parent and the child, then the modifier should be applied to the parent. In general most spacing between a parent and child are done with the padding
modifier applied to the parent.
Sibling Spacing #
In a Sibling relationship, there are more valid options for creating spacing as there is not a clear-cut "owner" of the spacing. If the siblings are in a directional layout (i.e. a Column
or Row
) it is appropriate to use Arrangement.spaceBy
and/or Spacer
components. Use Arrangement.spaceBy
on the parent layout if the siblings have equal space between each other. Use a Spacer
composable if the spacing is irregular. Spacer
can also be used in conjunction with Arrangement.spacedBy
as a one-off inside an otherwise uniformly spaced layout.
When using the Spacer
composable, adding the appropriate height
or width
modifier will create the expected spacing. It can be beneficial to create Spacer
overloads that accept a height or width directly to simplify their use. Compare the existing Spacer(modifier = Modifier.height(8.dp)
with the streamlined Spacer(height = 8.dp)
.
Here is this idea in code and can be copied for use in any project.
@Composable
fun Spacer(
modifier: Modifier = Modifier,
height: Dp = 0.dp,
width: Dp = 0.dp,
) {
androidx.compose.foundation.layout.Spacer(
modifier = modifier.size(width, height)
)
}
Touch Targets and Indicators #
One situation to be aware of where fixed spacing can get more complicated is when interactive elements need touch indicators. A common occurrence of this scenario is a list of elements that "appear" to have padding applied from the parent component. However if the padding is applied in that way, when the list items are clicked, the indicator will stop short of the screen edge. In this case, the end result requires moving the padding from the parent to the child. While this could seem in contradiction with the previous statement on ownership, it is important to consider that the touch indicator is owned by the child. This implies that the additional space does in-fact belong to the child and not the parent.
Conclusion #
Creating readable and consistent layouts in Jetpack Compose can be complex and no approach will be comprehensive. However, this framework makes it easier to think through what spacing approaches to apply and where. The result will be layouts that are more consistent and maintainable. Until next time, thank you!
Did you find this content helpful?
Please share this post and be sure to subscribe to the RSS feed to be notified of all future articles!
Want to go above and beyond? Help me out by sending me $1 on Ko-fi. It goes a long way in helping run this site and keeping it advertisement free. Thank you in advance!