androidandroid-jetpack-composeandroid-jetpack-compose-layout

Why adding and subtracting Compose Placeable constraint behave differently?


When I set my pleaceable constraint as below.

LazyColumn(
    contentPadding = PaddingValues(all =64.dp),
    verticalArrangement = Arrangement.spacedBy(16.dp),
) {


    val adjust = 32.dp

    item {
        Divider(modifier = Modifier.height(20.dp))
    }
    item {
        Divider(modifier = Modifier.height(20.dp)
            .layout { measurable, constraints ->
                val placeable =
                    // -32.dp offset constraint
                    measurable.measure(constraints.offset(-adjust.roundToPx()))

                layout(placeable.width, placeable.height
                ) { placeable.place(0, 0) }
            }
        )
    }
    item {
        Divider(modifier = Modifier.height(20.dp)
            .layout { measurable, constraints ->
                val placeable =
                    // +32.dp offset constraint
                    measurable.measure(constraints.offset(adjust.roundToPx()))

                layout(placeable.width, placeable.height
                ) { placeable.place(0, 0) }
            }
        )
    }
}

The 3 rows of items result (as shown in the image) below whereby

  1. First row is the reference
  2. Second row shorted on the right side by 32.dp
  3. Third row, increase both sides by 16.dp

Why does the second and third-row behavior differs i.e. 2nd row shrink just on the right, while 3rd row expands on both side equally?

enter image description here


Solution

  • It's not exactly about adding or subtracting, these operations work as expected when they return values in min-max bounds of Constraints.

    It's because setting layout width/height a Composable out of Constraints min-max bounds results it's being placed as the half of difference between Contraints and the value used in layout() function.

    If the content chooses a size that does not satisfy the incoming Constraints, the parent layout will be reported a size coerced in the Constraints, and the position of the content will be automatically offset to be centered on the space assigned to the child by the parent layout under the assumption that Constraints were respected.

    When layout width is bigger than constraints.maxWidth

    (constraints.maxWidth-layout(width))/2 which means placed with offset to the left

    when layout width is smaller than constraints.minWidth

    (constraint.minWidth-layout(width))/2 which means placed with offset to the right.

    You can refer this tutorial for more samples, information and examples about how layout, and Constraints work.

    https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/blob/master/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter3_layout/Tutorial3_2_4ConstraintsBounds.kt

    https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/blob/master/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter3_layout/Tutorial3_2_5SiblingConstraints.kt

    https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/blob/master/Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter3_layout/Tutorial3_2_6ConstrainAndOffset1.kt

    Also when you change as in snippet below it will be more clear to see how it works

    enter image description here

    @Preview
    @Composable
    private fun LayoutConstraintsSample1() {
        val density = LocalDensity.current
    
        LazyColumn(
            modifier = Modifier
                .padding(top = 40.dp)
                .padding(horizontal = 40.dp)
                .border(1.dp, Color.Red),
            verticalArrangement = Arrangement.spacedBy(16.dp),
        ) {
    
            val width = with(density) {
                300f.toDp()
            }
    
            val adjust = 32.dp
    
            item {
                Divider(modifier = Modifier.height(20.dp))
            }
            item {
                Divider(modifier = Modifier
                    .height(20.dp)
                    .layout { measurable, constraints ->
                        val placeable =
                            // -32.dp offset constraint
                            measurable.measure(constraints.offset(-adjust.roundToPx()))
    
                        layout(
                            placeable.width, placeable.height
                        ) { placeable.place(0, 0) }
                    }
                )
            }
    
    
            item {
                Divider(modifier = Modifier
                    .width(width)
                    .height(20.dp)
                    .layout { measurable, constraints ->
                        val placeable =
                            measurable.measure(
                                constraints.copy(
                                    minWidth = 300,
                                    maxWidth = 300
                                )
                            )
    
                        layout(
                            200, placeable.height
                        ) { placeable.place(0, 0) }
                    }
                )
            }
    
            item {
                Divider(modifier = Modifier
                    .width(width)
                    .height(20.dp)
                    .layout { measurable, constraints ->
                        val placeable =
                            measurable.measure(
                                constraints.copy(
                                    minWidth = 300,
                                    maxWidth = 300
                                )
                            )
    
                        layout(
                            400, placeable.height
                        ) { placeable.place(0, 0) }
                    }
                )
            }
        }
    }
    

    Placable width is same(300px) for both instances

    enter image description here

    @Preview
    @Composable
    private fun LayoutConstraintsSample2() {
        val density = LocalDensity.current
    
        LazyColumn(
            modifier = Modifier
                .padding(top = 40.dp)
                .padding(horizontal = 40.dp)
                .border(1.dp, Color.Red),
            verticalArrangement = Arrangement.spacedBy(16.dp),
        ) {
    
            val width = with(density) {
                300f.toDp()
            }
    
            val adjust = 32.dp
    
            item {
                Divider(modifier = Modifier.height(20.dp))
            }
            item {
                Divider(modifier = Modifier
                    .height(20.dp)
                    .layout { measurable, constraints ->
                        val placeable =
                            // -32.dp offset constraint
                            measurable.measure(constraints.offset(-adjust.roundToPx()))
    
                        layout(
                            placeable.width, placeable.height
                        ) { placeable.place(0, 0) }
                    }
                )
            }
    
    
            item {
                Divider(modifier = Modifier
                    .width(width)
                    .height(20.dp)
                    .layout { measurable, constraints ->
                        val placeable =
                            measurable.measure(
                                constraints.copy(
                                    minWidth = 300,
                                    maxWidth = 300
                                )
                            )
    
                        layout(
                            200, placeable.height
                        ) { placeable.place(0, 0) }
                    }
                )
            }
    
            item {
                Divider(modifier = Modifier
                    .width(width)
                    .height(20.dp)
                    .layout { measurable, constraints ->
                        val placeable =
                            measurable.measure(
                                constraints.copy(
                                    minWidth = 300,
                                    maxWidth = 300
                                )
                            )
    
                        layout(
                            400, placeable.height
                        ) { placeable.place(0, 0) }
                    }
                )
            }
        }
    }