Exploring Jetpack Compose: Modifiers

E

Within Android Studio 4.0 Canary 1 we can start exploring Jetpack compose, a new way to build the UI for your android applications in a declarative manner. To get started with jetpack compose, there is a great tutorial on the official developer site. In this series of articles I want to dive into each of the components that are available, exploring how we can utilise each of them within our applications.


When it comes to using the different components available within Jetpack Compose, you may notice that a lot of components allow you to to provide a Modifier reference. As the name entails, these modifiers are used to modifier certain aspects of how the component is displayed on screen. The Modifier itself is an interface, so we don’t directly instantiate an instance of that – instead we use the provided subclasses to decorate our component in some way. And when it comes to applying these modifiers, we can supply either a single modifier for our component or chain multiple modifiers together to apply multiple decorations to view components.

With that in mind, let’s take a look at the available modifiers and how they can be used to decorate supporting components within our layout.


Layout Modifiers

Currently all modifiers that are available within the Jetpack Compose API are layout modifiers. The LayoutModifier interface extends from the initial Modifier interface, currently that is the only interface that does so (maybe there will be different modifier types added in future). The layout modifier interface contains a collection of function definitions which are to be overridden by the modifier that is implementing the interface. These function definitions are used to calculate how the view should be laid out based on the provided modifications.

Spacing modifier

The Spacing modifier can be used to add spacing to the outside edges of our component, acting as Padding for the component that it wraps therefore increasing the size of the component itself.

There are two functions which can be used for constructing a Spacing instance. The first allows us to pass a dp value to apply spacing to every side of the component.

fun Spacing(all: Dp = 0.dp): LayoutModifier

Followed by a function where e can provide individual dp values for each side of the wrapped component, allowing us to have finer control over the spacing that we are applying.

fun Spacing(
    left: Dp = 0.dp,
    top: Dp = 0.dp,
    right: Dp = 0.dp,
    bottom: Dp = 0.dp
): LayoutModifier

For example, we could apply some Spacing to our component as shown below using one of the above functions.

Text(
    modifier = Spacing(left = Dp(40f), right = Dp(40f)),
    text = "Hello",
    style = TextStyle(color = Color.White)
 )

We can see here that the Text component has had the specified spacing applied to both the left and right side.


Size modifier

The Size modifier allows us to set an exact value for both the width and the height of the desired component. When it comes to setting the size modifier there are a collection of functions that can be used.

To begin with, we can set a fixed width and height of our component:

fun Size(width: Dp, height: Dp): LayoutModifier

We can also create a modifier that enforces minimum values for both the width and height of our component:

fun MinSize(minWidth: Dp, minHeight: Dp): LayoutModifier

We can also do the same when it comes to enforcing values for both the maximum width and height of our component:

fun MaxSize(maxWidth: Dp, maxHeight: Dp): LayoutModifier

There are also modifier functions available that allow us to set individual values for either the width or height, along with their corresponding min/max functions.

fun Width(value: Dp): LayoutModifier
fun MinWidth(value: Dp): LayoutModifier
fun MaxWidth(value: Dp): LayoutModifier

fun Height(value: Dp): LayoutModifier
fun MinHeight(value: Dp): LayoutModifier
fun MaxHeight(value: Dp): LayoutModifier

Using any of the above Size modifiers allow us to enforce dimension constraints for our components. As a quick example, utilising one of these will give us results like the below (dependant on the Size modifier that we are using):

Text(
    modifier = Size(width = Dp(60f), height = Dp(60f)),
    text = "Hello",
    style = TextStyle(color = Color.White)
)

Expanded modifier

The Expanded modifier allows us to enforce components to expand in order to fill their parent containers. This modifier provides us with ways to fill the entire container, or fill the width / height individually. There are currently three expanded modifiers available so let’s take a look at a quick example to see how each of these might look within our project.

To begin with, let’s say we have this Card component wrapped within a Container:

Container(modifier = Size(120.dp, 120.dp)) {
    Card(
        color = Color.White,
        children = {
            Text("Hello")
        }
    )
}

In this case we might want to have our Card fill our container in some way. For this we could use the Expanded modifier to achieve this. Passing Expanded here will make our Card component expand to fill both the width and height of its parent.

Container(modifier = Size(120.dp, 120.dp)) {
    Card(
        modifier = Expanded,
        ...
    )
}

In some cases though, we might not want to expand to fill both the width and height of the parent. In these cases we can make use of both the ExpandedHeight or ExpandedWidth modifiers to fill each of these constraints individually:

Container(modifier = Size(120.dp, 120.dp)) {
    Card(
        modifier = ExpandedHeight,
        ...
    )
}
Container(modifier = Size(120.dp, 120.dp)) {
    Card(
        modifier = ExpandedWidth,
        ...
    )
}

Aspect Ratio Modifier

Using the aspect ratio modifier allows us to enforce a ratio for the sizing of our component, meaning that our components height and width will expand to fill the specified aspect ratio.

The Aspect ratio modifier function takes a single float value that represents that aspect ratio that we wish to enforce.

fun AspectRatio(
    @FloatRange(from = 0.0, fromInclusive = false) value: Float
): LayoutModifier

So for example, if we wished to use the 9:16 aspect ratio then we could pass the corresponding float value for that ratio :

Card(
    modifier = AspectRatio(0.56f),
    ...
)

Chaining modifiers

As well as enforcing individual modifiers onto the components of our layout, we can also chain modifiers to apply multiple constraints at a time. For example, this may be useful in cases where we wish to set a minimum height for our view as well as providing some spacing for it also.

Card(
    modifier = MinHeight(Dp(40f)).wraps(Spacing(Dp(40f))),
    ...
)

In some cases there will be no effect chaining constraints. For example, if we enforce an Aspect Ratio modifier on our component and try to set a maximum size on it, then the aspect ratio will not allow for this size constraint to be set

Card(
    modifier = AspectRatio(0.56f).wraps(MaxSize(Dp(10f), Dp(10f))),
    ...
)

In the above examples we’ve looked at the currently available Layout Modifiers and how they can be applied to the components of our layout. Alongside the about Layout Modifiers we also have the FlexScope modifier, however this is specific to the Flex layout components so we will at that modifier when we cover that layout component.

Are you already using these modifiers yourself or have any questions on how they work? Feel free to reach out if so!

[twitter-follow screen_name=’hitherejoe’ show_count=’yes’]

About the author

hitherejoe

1 Comment

Leave a Reply to Exploring Jetpack Compose: Row & Column – Joe Birch Cancel reply

By hitherejoe