Modifier is an important concept in Compose that can make your UI look more professional and visually appealing.

1. Why use Modifier?

In a regular View architecture, controls exist as instance objects that can be dynamically configured after instantiation, but the Composable is essentially a function that can only be configured with parameter passing at the same time it is called, and the parameter signature becomes very long without Modifier (although Kotlin supports default parameters).

Modiifer solves this problem by acting like a configuration file for The Composable, where the style and behavior of the Composable can be uniformly configured.

2. Modifier is an ordered chain of calls

Modifier “decorates” our Composable with chain calls, adding Background, Padding, onClick events, etc.

Each operator on the chain creates an Element, and the entire call chain is an ordered execution unit of a set of elements:

Text(
    "Hello, World!",
    Modifier.padding(16.dp) // Outer padding; outside background
        .background(color = Color.Green) // Solid element background color
        .padding(16.dp) // Inner padding; inside background, around text
)
Copy the code

As above, calling the two padding on the chain is not an override relationship, but rather works sequentially.

PaddingModifier create PaddingModifier

class PaddingModifier(val start: Dp, val top : d'p...) : Modifier.Element fun Modifier.padding(all: Dp) = this.then(PaddingModifier(start = all, top = all, xxx))Copy the code

Then is used to combine two modifiers and keep them executed in sequence.

open infix fun then(other: Modifier): Modifier
Copy the code

The Modifier is similar to the RxJava Observable, which is based on the idea of functional programming by concatenating operators into a group of executable functions that are executed during a Composable render.

3. Use the Modifier to decorate the Composable

Modifier operators (API) although the number of, but clear semantics, it is not difficult to use. Here is an example of how to use Modifier to decorate our Composable.

We tried to implement an Item of the concern list with Compose as follows

@Composable
fun Plain(a) {
    Row(modifier = Modifier.fillMaxWidth()) {
        Image(
            modifier = Modifier.size(40.dp),
            bitmap = imageResource(id = R.drawable.miku),
            contentDescription = null.// decorative
        )
        Column(modifier = Modifier.weight(1f)) {
            Text(text = name, maxLines = 1)
            Text(text = desc, maxLines = 1)
        }
        Text("Follow", Modifier.padding(6.dp))
    }

}
Copy the code

Next, we will decorate it step by step through the Modifier. At the end of the article, the UI will achieve the effect of the second picture below.

3.1 Overall Layout

Modifier.padding

We use theRow,ColumnThe elements within Item are laid out basically, but there is a lack of spacing between the elements

Compose adds Padding between composables using the Modifier

@Composable
fun Decorated(a) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .preferredHeightIn(min = 64.dp)
            .padding(8.dp) / / clearance
            .border(1.dp, Color.LightGray, RoundedCornerShape(4.dp))
            .padding(8.dp) / / clearance) {... }}Copy the code

As above, we add the Padding to the Item as a whole. There is a padding before and after the border, indicating the outer and inner spacing respectively. Compared to the traditional layout with Margin and Padding, the Modifier has only Padding, which plays different roles according to the position in the call chain, making it easier to use.

Modifier.border

Border defines the border, and RoundedCornerShape is a Shape that specifies that the Shape of the border is a rounded rectangle.

We can also call background twice to achieve the border effect:

modifier = Modifier
	.background(Color.LightGray) 
    .padding(1.dp) // Create a border between two backgounds
    .background(Color.White)
Copy the code

Modifier.preferredHeight / Modifier.preferredHeightIn

PreferedXXX is used to set the initial size, for example, the preferedHeight can set the default height of the Composable, which may be overwritten by other constraints. To avoid overwriting the height, use the Modifier. Height to set a fixed value

In this example, the preferedHeightIn is used, and you can set minHeight and maxHeight.

Modifier.fillMaxWidth

FillMaxWidth fills the entire parent container, equivalent to match_parent in a traditional layout

3.2 Pass the Modifier into the parameter

Fill the contents of the Row, from left to right, with the avatar, text, and buttons

@Composable
fun Decorated(a) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .preferredHeight(64.dp)
            .padding(8.dp)
            .border(1.dp, Color.LightGray, RoundedCornerShape(4.dp))
            .padding(8.dp)

    ) {

        Avatar( // The avatar section
            modifier = Modifier
                .padding(4.dp)
                .align(Alignment.CenterVertically)
        )

        Info( // The text part
            Modifier
                .weight(1f)
                .align(Alignment.CenterVertically)
        )

        FollowBtn( / / button
            Modifier.align(Alignment.CenterVertically)
        )
    }
}
Copy the code

We will implement the concrete partition into a separate Composable, calling the Modifier in Row and passing it in.

When defining Composable in Compose, it is a good practice to reserve parameter positions for the Modifier

Modifier provides callers with the opportunity to modify the style of child elements, but more importantly, there are some operators that can only be called externally.

Modifier.align

The Modifier operators are extension functions and are not defined together. Operators are defined in different Spaces. It is possible to restrict certain operators to be used only in specific parent Comopsable to avoid misuse.

interface RowScope {
    fun Modifier.align(alignment: Alignment.Vertical)
}
Copy the code

As above, align can only be called in Row to set how child elements are aligned vertically. Child elements in its parent container don’t care about how to align, so in setting the align external (after Alignment. CenterVertically), to continue to use child elements.

Modifier.weight

Weight can also be called within a Row, assigning the child elements a proportion within the Row, similar to the layout_weight LinearLayout. In this case, let the text in the middle take up all the space

3.3 Profile Picture

We rounded the head picture and added a border to improve the overall visual effect.

@Composable
fun Avatar(modifier: Modifier) {
    Image(
        modifier = modifier
            .size(50.dp)
            .clip(CircleShape)
            .border(
                shape = CircleShape,
                border = BorderStroke(
                    width = 2.dp,
                    brush = Brush.linearGradient(
                        colors = listOf(blue, teal200, green200, orange),
                        start = Offset( 0f.0f),
                        end = Offset(100f.100f)
                    )
                )
            )
            .border(
                shape = CircleShape,
                border = BorderStroke(4.dp, SolidColor(Color.White))
            ),
        bitmap = imageResource(id = R.drawable.miku),
        contentDescription = null.// decorative)}Copy the code

Modifier.size

First size(50.dp) sets the overall size of the image

Modifier.clip

Clip Is used to clip an image to a specified shape. In the example, clip(CircleShape) is used to clip an image to a circle

Modifier. Border Call order

The border of the image is made up of two parts, the outer colored part, and the inner white border, so there are two borders () in the call chain. The order of the two border calls requires special attention. Border indicates that the border is added to the subsequent call, so it takes effect after the previous call. So the order of the border calls in the example is as follows:

Modifier
.border() //2dp color border
.border() //4dp white border
Copy the code

BorderStroke & Brush

Border fills the border color with a BorderStroke.

The outer border is filled with a gradient of multiple colors using Brush.Lineargradient, with start and end representing the color range

BroderStroke(
	brush = Brush.linearGradient(
    	 colors = listOf(blue, teal200, green200, orange),
     	start = Offset( 0f.0f),
     	end = Offset(100f.100f)))Copy the code

The inner border is filled with SolidColor to fix the color

BorderStroke(brush = SolidColor(Color.White))
Copy the code

3.4 Text Part

@Composable
fun Info(modifier: Modifier) {
    Column(modifier = modifier) {
        Text(
            text = name,
            color = Color.Black,
            maxLines = 1,
            style = TextStyle(
                fontWeight = FontWeight.Bold,
                fontSize = 16.sp,
                letterSpacing = 0.15.sp
            )
        )
        Text(
            text = desc,
            color = Color.Black.copy(alpha = 0.75 f),
            maxLines = 1,
            style = TextStyle( // here
                fontWeight = FontWeight.Normal,
                fontSize = 14.sp,
                letterSpacing = 0.25.sp
            )
        )
    }
}
Copy the code

Instead of using the Modifier, many font styles are set using the Text properties and TextStyle Settings

Text color

For example, the color class for Compose can be used to set transparency: color.black.copy (alpha = 0.75f)

TextStyle

TextStyle allows you to set the font, size, and so on. In this example, fontWeight sets bold

textDecoration

Although it is not used in this example, Text also has an important property textDecoration, which is used for more targeted “decoration” of the Text, such as underlining, stripling, etc

textDecoration = TextDecoration.Underline
Copy the code

3.5 the button

While Compose provides a dedicated Button implementation Button, it is also possible to implement buttons using Text with greater customizability.

@Composable
fun FollowBtn(modifier: Modifier) {
    val backgroundShape: Shape = RoundedCornerShape(4.dp)

    Text(
        text = "Follow",
        style = typography.body1.copy(color = Color.White),
        textAlign = TextAlign.Center,
        modifier =
        modifier
            .preferredWidth(80.dp)
            .clickable(onClick = {})
            .shadow(3.dp, shape = backgroundShape)
            .clip(backgroundShape)
            .background(
                brush = Brush.verticalGradient(
                    colors = listOf(
                        Red500,
                        orange700,
                    ),
                    startY = 0f,
                    endY = 80f
                )
            )
            .padding(6.dp)
    )
}
Copy the code

Modifier.background

After adding a gradient background color and shadow to the button, it becomes more imitative and textured. Gradient color is also added to the background through Brush

Modifier.shadow

Shadow specifies the location of the shadow in the call chain. The shadow itself also takes up area, so call it before background to avoid the shadow entering the background color area

Modifier.click

Text can implement buttons instead of Button because the Modifier provides Click to make the Composable handle the onClick event

4. The last

Through the above example, I believe that we have mastered the basic use of the Modifier, Modifier and a lot of advanced API, subsequent opportunities to share with you. Modifier in the design of the decorative model, FP and other programming ideas, ingenious ideas, worth learning.


“Reference”

Sample Code

More Modifiers API Reference