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

An overview of the

This note continues to learn about the layout in Compose. Click the link to view the official document directly.

Through this study notes, we can learn the following knowledge:

  • Adaptive layout
  • Custom layouts – Use layout modifiers
  • Custom Layout – Use Layout to customize the subitems
  • Custom layout – Implement a layout similar toFlowCustom layout

Adaptive layout

Because there are so many different types of Android devices and screen sizes and types, it is important to consider different screen orientations and device sizes during development. Using constraints allows you to understand the parent constraints and design the layout accordingly. Using BoxWithConstraints allows you to find measurement conditions within the scope of the content. Then use these measurements to design different layouts. As follows:

    @Composable
    private fun LayoutWithBoxWithConstrains(a){
        Box(modifier = Modifier
            .fillMaxSize()
            .background(color = Color.LightGray)) {
            BoxWithConstraints() {
                if(maxHeight > maxWidth){
                    // The vertical is larger
                    Column(modifier = Modifier.fillMaxSize(),
                        horizontalAlignment = Alignment.CenterHorizontally
                        ) {
                        Text(text = "Vertical alignment")
                        Spacer(modifier = Modifier.height(10.dp))
                        Text(text = "Vertical alignment")
                        Spacer(modifier = Modifier.height(10.dp))
                        Text(text = "Vertical alignment")}}else{
                    // The horizontal is relatively large
                    Row(modifier = Modifier.fillMaxSize()) {
                        // The left header is the same as the right
                        Column(modifier = Modifier
                            .weight(1f)
                            .fillMaxHeight(),
                            verticalArrangement = Arrangement.Center
                            ) {
                            Text(text = "Show title list on the left")
                        }

                        Column(modifier = Modifier
                            .weight(3f)
                            .background(color = Color.Green)
                            .fillMaxHeight(),
                            verticalArrangement = Arrangement.Center
                            ) {
                            Text(text = "Display content area on the right")}}}}}}Copy the code

In the above code, we determine the maximum width and maximum height available at present. If the width is large, we consider it landscape and the screen is divided into left and right sides. If the height is large, we consider it portrait and only one column of content is displayed.

Custom layout

Use the layout modifier

Modify the measurement and layout of the elements by using the layout modifier. This is a lambda that takes two parameters — measurable (Measurable) and constraints (constraints).

The following code demonstrates the effect of paddingFromBaseline in Text:

To do this, we need to first understand the specific effects we are trying to achieve:

As you can see from the renderings above, when we set the distance based on the text baseline, our layout changes accordingly: since the text baseline is different from the padding, the padding simply adds the value we set to the height of the current element. But the literal baseline itself is in the height of the element, so when we set the height based on the literal baseline, our actual element height becomes:

Set distance – Height of text base + height of text (height of original element)

    fun Modifier.firstBaselineToTop(distance: Dp) = layout { measurable, constraints ->
        // This method measures the layout with the given constraints and returns the placement layout with the new size
        val placeable = measurable.measure(constraints)
        
        // Check to include the alignment of the Text baseline, which is provided in the Textcheck(placeable[FirstBaseline] ! = AlignmentLine.Unspecified)// Return the position of the text baseline if the alignment based on the text baseline is already provided
        val firstBaseline = placeable[FirstBaseline]
        
        // Subtract the height of the text base from the height we passed in to get the height of the current element from the top
        val placeableY = distance.roundToPx() - firstBaseline
        
        // Set the new height of the current element
        val height = placeableY + placeable.height
        
        // Use the original width and the new height for the layout
        layout(placeable.width, height) {
            placeable.placeRelative(0, placeableY)
        }
    }
Copy the code

Once we have created our new custom layout, we can use this layout in Text, as shown below:

    @Composable
    private fun MyLayoutFirstBaselineTopTop(a) {
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Row(modifier = Modifier.fillMaxWidth()) {
                Text(
                    text = "This is the text on the left.",
                    textAlign = TextAlign.End,
                    modifier = Modifier
                        .weight(1f)
                        .background(color = Color.Green)
                        .firstBaselineToTop(32.dp)
                )
                Text(
                    text = "This is the text on the right.",
                    modifier = Modifier
                        .weight(1f)
                        .background(color = Color.Cyan)
                        .paddingFromBaseline(top = 32.dp)
                )

            }
        }
    }
Copy the code

The running effect is as follows:

As you can see from the above, the effect of our custom layout is roughly the same as using paddingFromBaseline.

Roughly the same because the height we set based on the text baseline is likely to be less than the text itself, so using the above formula to set the distance – the height of the text baseline + the height of the text (the height of the original element) will get a smaller value than the original text height. For example, setting the above 32. Dp to 5. Dp gives the following effect:

As you can see, the text on the left is no longer visible in the background, and the text on the right should have a special treatment for this situation, so it remains the original height. We can also make some judgments here, and if we don’t get the right height we can reset the height. Or if the computed placeableY is negative, force it to be 0.

Create a custom layout

If you need to measure more than one subitem, the layout element above cannot do so. You can use the layout combinable item, which allows for automatic measurement and placement of subitems. Higher level layouts such as Column and Row are built with layout combinable items. In the previous View system, creating this type of custom layout had to extend the ViewGroup and implement the measurement and layout functions.

Here’s how to build a custom layout that automatically jumps to the next line when the subitems are extended to the far right of the interface:

    @Composable
    fun WrapLayoutItem(
        modifier: Modifier,
        content: @Composable() - >Unit
    ) {
        Layout(
            content = content,
            modifier = modifier
        ) { measurables, constraints ->
            
            // Traverse measurements for each subitem
            val placeables = measurables.map { measurable ->
                // Measure each subitem according to the current constraints
                measurable.measure(constraints)
            }

            // Set the size of the layout as big as it can
            layout(constraints.maxWidth, constraints.maxHeight) {
                // The Y-axis coordinates of the current subterm
                var yPosition = 0
                // The X-axis coordinates of the current subterm
                var xPosition = 0
                // The height of the maximum subterm of each row
                var maxHeightRow = 0
                // The total height after the newline
                var maxHeightAll = 0

                // Place children in the parent layout
                placeables.forEach { placeable ->
                    // First judge whether the current need to wrap the line
                    if (xPosition + placeable.width > constraints.maxWidth) {
                        // Line feeds are required
                        maxHeightAll += maxHeightRow
                        maxHeightRow = 0
                        xPosition = 0
                        yPosition = maxHeightAll
                    }
                    // If the height of the current child is greater than the height of the largest child in the row, set the height of the largest child to the height of the child
                    if (maxHeightRow < placeable.height) {
                        maxHeightRow = placeable.height
                    }
                    // Position item on the screen
                    placeable.placeRelative(x = xPosition, y = yPosition)

                    // Record the y co-ord placed up to
                    xPosition += placeable.width
                    //yPosition += placeable.height}}}}Copy the code

In the above code, we first measure each subitem, and then we start the layout by iterating through each subitem and arranging its position. We want the subitems to automatically expand to the next line after they fill one line, and then we use this layout in our code:

    @Composable
    private fun LayoutWrapItem(a) {
        WrapLayoutItem(
            modifier =
            Modifier
                .padding(top = 10.dp)
                .fillMaxWidth()
        ) {

            Text(text = "Testing custom Layout 1", modifier = Modifier
                .size(80.dp)
                .background(color = Color.Green))

            Text(text = "Testing custom Layout 2", modifier = Modifier
                .size(150.dp)
                .background(color = Color.Blue))

            Text(text = "Testing custom Layout 3", modifier = Modifier
                .size(130.dp)
                .background(color = Color.Gray))

            Text(
                text = "Testing custom Layout 4",
                modifier = Modifier
                    .size(280.dp)
                    .background(color = Color.Black)
            )
        }
    }
Copy the code

In the above code, we set a fixed size for each of the children of the custom layout, and the size is different. The final result is as follows:

As you can see, the end result is not what we thought. The height is set to the size we set, but the width is clearly set to the width of its parent. This problem arises because we passed constraints directly to the measurement, which is the parent’s size constraint, and we directly used the parent’s size constraint to measure the child, resulting in the child’s width using the parent’s size.

Knowing what the problem is, we can set the layout constraint of the subitem accordingly. In the following code, we force the layout constraint of the subitem to be set when measuring the subitem, as shown below:

    @Composable
    fun WrapLayoutItem(
        modifier: Modifier,
        content: @Composable() - >Unit
    ) {
        Layout(
            content = content,
            modifier = modifier
        ) { measurables, constraints ->

            // Traverse measurements for each subitem
            val placeables = measurables.map { measurable ->
                // Measure each subitem according to the current constraints
                measurable.measure(constraints = constraints)
            }

            // Set the size of the layout as big as it can
            layout(constraints.maxWidth, constraints.maxHeight) {
                // The Y-axis coordinates of the current subterm
                var yPosition = 0
                // The X-axis coordinates of the current subterm
                var xPosition = 0
                // The height of the maximum subterm of each row
                var maxHeightRow = 0
                // The total height after the newline
                var maxHeightAll = 0

                // Place children in the parent layout
                placeables.forEach { placeable ->
                    // First judge whether the current need to wrap the line
                    if (xPosition + placeable.width > constraints.maxWidth) {
                        // Line feeds are required
                        maxHeightAll += maxHeightRow
                        maxHeightRow = 0
                        xPosition = 0
                        yPosition = maxHeightAll
                    }
                    // If the height of the current child is greater than the height of the largest child in the row, set the height of the largest child to the height of the child
                    if (maxHeightRow < placeable.height) {
                        maxHeightRow = placeable.height
                    }
                    // Position item on the screen
                    placeable.placeRelative(x = xPosition, y = yPosition)

                    // Record the y co-ord placed up to
                    xPosition += placeable.width
                    //yPosition += placeable.height}}}}Copy the code

Rerun the above code as follows:

As you can see, it’s already getting the results we want.