This is the first day of my participation in the August Challenge. For details, see:August is more challenging

This learning note is mainly based on the official document to learn the layout of Android Compose. By learning this note, you can roughly understand the three layouts of Row,Column and Box in Compose, and also learn how to set the background color, size and other properties of the layout. Basic knowledge (Compose layout | Jetpack Compose | Android Developers) click on the link to view the official document directly, enter text below:

An overview of the

This study note is mainly about the layout in Compose. The main content is from the official document. You can directly click the link above to access the official document.

The target

The layout system in Compose serves two purposes:

  1. Achieve high performance. (In a typical Android View system, you should always try to avoid multiple layers of layout nesting, which can cause the layout to be drawn multiple times, affecting performance, but the way layouts are measured in Compose is called intrinsic property measurement, which achieves high performance by avoiding multiple measurements of layout children.) This is the basic concept, but we’ll learn how to do it later.

  2. Makes it easy for developers to write custom layouts. (In the common Android View system, it is tedious to define the layout. On the one hand, we should consider the process of measure, on the other hand, we should consider the process of layout.) It’s the same basic concept that needs to be compared later.

Combinable function

Composable functions are the basic building blocks of Compose, returning functions for Unit that describe parts of the interface. Such a function can take some input and then generate the information displayed on the screen based on the input.

A composable function may emit multiple interface elements, and if we do not specify a proper layout for these elements, the final appearance may not be what we want. The following code demonstrates that emitting two texts in a composable function may not look like what we want:

    @Composable
    private fun noLayout(){
        Text(text = "the first text")
        Text(text = "the second test")
    }
Copy the code

The combinable function above does not provide a layout for how to arrange the two elements, so the end result is that the two texts will be stacked together, similar to FrameLayout:

Vertical arrangement of elements (Column)

Columns allow you to place multiple elements vertically on the screen, similar to the effect of Oritation :vertical in LinearLayout.

    @Composable
    private fun columnLayout(){
        Column() {
            Text(text = "the first text",fontSize = 20.sp,color = Color.Black)
            Text(text = "the second test",fontSize = 16.sp,color = Color.Red)
        }
    }
Copy the code

The above code wraps the columns around the two texts. Here’s what happens when you use columns:

Horizontal arrangement of elements (Row)

Use Row to place its children horizontally on the screen, similar to the effect of oritation:horitation in LinearLayout:

    @Composable
    private fun rowLayout(){
        Row() {
            Text(text = "the first text",fontSize = 20.sp,color = Color.Black)
            Text(text = "the second test",fontSize = 16.sp,color = Color.Red)
        }
    }
Copy the code

The effect is as follows:

Layering elements (Box)

Use Box to place one element on top of another, as follows:

    @Composable
    private fun boxLayout(a){
        Box() {
            Text(text = "the first text",fontSize =30.sp,color = Color.Black)
            Text(text = "the second test",fontSize = 16.sp,color = Color.Red)
        }
    }
Copy the code

The effect is as follows:

While the above effect looks similar to the one you would have done without using any, using Box allows you to specify some additional parameters that you would not have achieved without using any layout. The following code specifies the alignment of the subitems:

    @Composable
    private fun boxLayout(a){
        Box(contentAlignment = Alignment.Center) {
            Text(text = "the first text",fontSize = 30.sp,color = Color.Black)
            Text(text = "the second test",fontSize = 12.sp,color = Color.Red)
        }
    }
Copy the code

The effect is as follows:

In general, the three building blocks above can be used to meet the requirements, which can be achieved by combining these layouts with combinable functions, and since there is no need to worry about multiple rendering, we can combine these layouts with little concern for performance.

Improve the parameters

We have shown that we can set parameters in Box to change the position of the subitems. We can also specify parameters in Column and Row to achieve different effects. By specifying the horizontalArrangement and verticalAlignment parameters (for a Row) or the verticalArrangement and horizontalAlignment parameters (for a Column), You can specify the location of its children.

  1. The following code specifiesColumnThe effect of the middle term being vertically at the bottom and horizontally at the middle:
    @Composable
    private fun columnLayoutChild(a){
        Column(
            verticalArrangement = Arrangement.Bottom,
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier.fillMaxSize()
        ) {
            Text(text = "the first text",fontSize = 30.sp,color = Color.Black)
            Text(text = "the second test",fontSize = 12.sp,color = Color.Red)
        }
    }
Copy the code

Since columns by default resize themselves according to the size of their children, the modifer argument is used to specify that the size of a Column is full of its parent container, otherwise verticalArrangement = arrangement. Bottom has no effect.

  1. The following code specifiesRowThe position of the subitem:
    @Composable
    private fun rowLayoutChild(a){
        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.End,
            modifier = Modifier.fillMaxSize()
        ) {
            Text(text = "the first text",fontSize = 30.sp,color = Color.Black)
            Text(text = "the second test",fontSize = 12.sp,color = Color.Red)
        }
    }
Copy the code

In the above code, we first specify the size of the Row to fill its parent container, and then specify that the children of the Row should be right-most horizontally and centered vertically. The effect is as follows:

  1. Here’s what we’re familiar withRowandColumnThe specifiedweightProperty, as shown below:
    @Composable
    private fun columnWeightChild(a) {
        Column(modifier = Modifier.fillMaxSize()) {
            Text(
                text = "the first text",
                fontSize = 30.sp,
                color = Color.Black,
                modifier = Modifier.weight(1f)
                    .background(color = Color.LightGray)
            )
            Text(
                text = "the second test",
                fontSize = 12.sp,
                color = Color.Red,
                modifier = Modifier.weight(1f)
                    .background(color = Color.DarkGray)
            )
        }
    }
Copy the code

The effect is as follows:

The modifier

In the argument section above, we’ve seen some attributes, just like we define attributes in an XML layout. We need to add more effects, so we need more attributes. Now we’ll learn more about the attributes that the system provides for us, also called modifiers.

Modifiers are standard Kotlin objects that can be created by calling an Modifier class function, or by connecting multiple Modifier functions to form a combination effect.

sizeSpecify the size

We’ve already used the size modifier to specify the size of an item, as shown below:

    @Composable
    private fun sizeColumn(a){
        Column(
            modifier = Modifier
                .size(width = 200.dp, height = 200.dp)
                .background(color = Color.Red),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = Width and height: 200dp,
                color = Color.White
                )
        }
    }
Copy the code

In the code above, we give the Column a size, width, and height of 200dp, and we also give it a color so you can see the effect. You can see that the size and background attributes are Modifier class functions, the two functions can be combined to achieve the effect we want. Here is the result:

The above illustrates specifying the size of a Column. By default, children should conform to the parent constraint, that is, the size of children cannot exceed the size of the parent. By default, if a child exceeds the size of its parent, it does not take effect, as shown below:

    @Composable
    private fun bigParentSize(a) {
        Box(
            modifier = Modifier
                .size(300.dp, 600.dp)
                .background(color = Color.LightGray),
            contentAlignment = Alignment.Center
        ) {
            Column(
                modifier = Modifier
                    .size(200.dp)
                    .background(color = Color.DarkGray),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(
                    text = "The child is greater than the parent.",
                    color = Color.Red,
                    modifier = Modifier
                        .size(width = 50.dp, height = 300.dp)
                        .background(color = Color.Green)
                )
            }
        }
    }
Copy the code

In the above code, we specify that the Column is 200dp wide and 200DP high, and that the child Text is 300DP high, which is larger than the parent. In theory, we should not be able to see the content in the Text, or only see part of the content, because part of the content is above the parent item, but in fact, we can still see the complete content in the Text, because the size of the child is restricted by the parent item, and the height of the child itself cannot exceed the height of the parent item. The effect is as follows:

requiredSizeBreak the parent’s size limit

If we simply don’t want the parent constraint and keep the child size, then we should use requiredSize() when setting the size. The size specified using this method is not subject to the parent constraint:

    @Composable
    private fun ignoreParentSize(a) {
        Box(
            modifier = Modifier
                .size(300.dp, 600.dp)
                .background(color = Color.LightGray),
            contentAlignment = Alignment.Center
        ) {
            Column(
                modifier = Modifier
                    .size(200.dp)
                    .background(color = Color.Blue),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Bottom
            ) {
                Text(
                    text = "The child is much larger than the parent.",
                    color = Color.White,
                    fontSize = 40.sp,
                    modifier = Modifier
                        .requiredSize(width = 80.dp, height = 500.dp)
                        .background(color = Color.Green)
                )
            }
        }
    }
Copy the code

In the above code, we still set the height of the child to be higher than the height of the parent, and to see the final effect, we set the Text size and set a larger parent Box outside. The final effect is as follows:

wrapContentSizeSet subitem alignment

In the above code, we set the size of the child item to be larger than the size of the parent item. By default, the text is displayed from top to bottom, so the top half of the text is invisible. What if we want to see the top half of the text? Instead, use the wrapContentSize property, as shown below:

    @Composable
    private fun ignoreParentSize(a) {
        Box(
            modifier = Modifier
                .size(300.dp, 600.dp)
                .background(color = Color.LightGray),
            contentAlignment = Alignment.Center
        ) {
            Column(
                modifier = Modifier
                    .size(200.dp)
                    .background(color = Color.Blue),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Bottom
            ) {
                Text(
                    text = "The child is much larger than the parent.",
                    color = Color.White,
                    fontSize = 40.sp,
                    modifier = Modifier
                        .requiredSize(width = 80.dp, height = 500.dp)
                        .background(color = Color.Green)
                        .wrapContentSize(align = Alignment.BottomCenter)
                )
            }
        }
    }

Copy the code

We use the same code as before, but we add the wrapContentSize property to the Text subitem, and specify align as bottom center, so that the top half of the Text is displayed as much as possible. The effect is as follows:

Note that the wrapContentSize property can only specify the alignment of the subitem itself. This is because there is less text in the subitem. If the bottom is centered, the upper part of the text will appear, but it will not.

As you can see, when there is more text, you can only see the text in the middle, not the text up and down.

Another note: You can use the align property of wrapContentSize to specify the alignment of the inner text when the number of text is small, but this does not take effect when the text exceeds the height of the subitem. As shown below, the following two images specify align = Alignment.Center:

In addition, how the text itself is aligned should be set using the textAlign property.

Alternatively, if you just want to set width or height, you can use methods like Width(), height(),requiredWidth(),requiredHeight(), and so on.

If we only need to fill the parent in width or height, we can use the fillMaxWidth(),fillMaxHeight(), fillMaxSize() methods, as shown below:

    @Composable
    private fun fillParent(a) {
        Column() {

            Box(
                modifier = Modifier
                    .size(200.dp, 100.dp)
                    .background(color = Color.Red),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "Full width of parent", color = Color.White, modifier = Modifier
                        .fillMaxWidth()
                        .background(color = Color.Gray)
                )
            }

            Spacer(modifier = Modifier.height(10.dp))

            Box(
                modifier = Modifier
                    .size(200.dp, 100.dp)
                    .background(color = Color.Yellow),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "Full width of parent",
                    color = Color.White,
                    modifier = Modifier
                        .fillMaxHeight()
                        .background(color = Color.Gray)
                )
            }

            Spacer(modifier = Modifier.height(10.dp))

            Box(
                modifier = Modifier
                    .size(200.dp, 100.dp)
                    .background(color = Color.Blue),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "Full of the parent term.",
                    color = Color.White,
                    modifier = Modifier
                        .fillMaxSize()
                        .background(color = Color.Gray)
                )
            }
        }
    }

Copy the code

The running effect is as follows:

paddingFromBaselineSet the distance based on the text baseline

For text, if you want to set the distance based on the text baseline, you can use paddingFromBaseline:

    @Composable
    private fun distanceBaseline(a) {
        Row() {
            Box (
                modifier = Modifier.background(color = Color.Gray)
            ){
                Text(
                    text = "Set distance based on text baseline",
                    modifier = Modifier
                        .paddingFromBaseline(
                            top = 100.dp,
                            bottom = 50.dp,
                        )
                        .background(color = Color.Red),
                    color = Color.White
                )

            }
            
            Spacer(modifier = Modifier.width(20.dp))

            Box (
                modifier = Modifier.background(color = Color.Gray)
            ){
                Text(
                    text = Sets the Padding "",
                    modifier = Modifier
                        .padding(
                            top = 100.dp,
                            bottom = 50.dp,
                        )
                        .background(color = Color.Red),
                    color = Color.White
                )

            }
        }

    }
Copy the code

offsetSet offset

To set the layout relative to its original position, set offset. The offset can be either positive or negative, as shown below:

    @Composable
    private fun paddingAndOffset(a){
        Column {
            Text(text = "This is a text.",
                modifier = Modifier
                    .background(color = Color.Gray)
                    .offset(x = 10.dp)
                )
            Text(text = "This is a text.",
                modifier = Modifier
                    .padding(start = 10.dp,top = 10.dp)
                    .background(color = Color.Red)
                )
        }
    }
Copy the code

As you can see from the above picture, using offset does not change the measurement of the composable.

Type safety in Compose

In Compose, there are modifiers that apply only to certain composable items, such as the weight modifier you learned above that applies only to Row and Column.

BoxIn thematchParentSize

When using a Box layout, if we want children to fill up the parent without affecting the size of the parent, we should use the matchParentSize modifier. This modifier only applies to the scope of the Box, meaning that the immediate children of the Box can use this modifier, as shown below:

    @Composable
    private fun matchParentSizeInBox(a){
        Box(modifier = Modifier.background(color = Color.Gray)) {
            Spacer(modifier = Modifier
                .background(color = Color.Cyan)
                .matchParentSize())
            Text(text = "This is a big text box.",modifier = Modifier.size(200.dp)
                .wrapContentSize(align = Alignment.Center),
                textAlign = TextAlign.Center,
                )
        }
    }
Copy the code

The above code runs as follows:

As you can see from the above, Box contains two children, Spacer and Text, with Spacer set to the same size as the parent and Text having its own size. The final size of Box is set to the size of the largest of all its children, and Spacer and size are the same as the size of the largest child (as you can see from the background color).

We learned earlier that fllMaxSize makes the child size the same as the parent, but the problem with fillMaxSize is that the child affects the parent size, as shown below:

    @Composable
    private fun fillMaxSizeInBox(a){
        Box(modifier = Modifier.background(color = Color.Gray)) {
            Spacer(modifier = Modifier
                .fillMaxSize()
                .background(color = Color.Cyan)
            )
            Text(text = "This is a big text box.",
                modifier = Modifier
                    .size(200.dp)
                    .background(color = Color.Green)
                    .wrapContentSize(align = Alignment.Center),
                textAlign = TextAlign.Center
                )
        }
    }
Copy the code

In the code above, Text also has its own size, and Spacer is set to fill the size of the parent, but the parent has no specific size, so this property reversely scopes the parent so that the parent also fills the size of its parent. The end result is as follows: