androidlayoutandroid-jetpack-compose

Jetpack Compose - extend child of layout beyond bounds


What I am looking for is a way to have the child of a layout element (row or column) extend beyond its parent's bounds in perpendicular direction. I want to do this because I want to have the row in the background have a color set, yet the surrounding area of the child element to be transparent.

Here is a visual representation of what I want to achieve: (would like to post an image but I don't have enough reputation yet)

  -------
  |     | <- extended element
  |     |
--|     |----------
| |     |         | <- row with background
| -------         |
-------------------

Ideally, a hypothetical parent of the row should respect the height of the extended child. This is because, to my understanding, the extended part would usually either be cut off by other components above it, or the edge of the screen (because it exceeds the bounds).

I tried two things already

Using onGloballyPositioned

With this method, it is probably possible some way but I didn't find a way to do it with the positional methods provided by the LayoutCoordinates lambda argument.

Additionally, I observed the child element to "jump" positions after first composition. I would like to avoid this if possible.

Using the method described in this answer

With this, I am able to change the position of the child element, as well as the row itself, but resizing does not seem to work.

Maybe there is something I am doing wrong somewhere, but this is what I got for now:

val itemWidth = 128
val itemHeight = 256

Row(
    modifier = Modifier.fillMaxWidth()
) {
    Box(
        modifier = Modifier
            .width(itemWidth.dp)
            .layout { measurable, constraints ->
                val placeable = measurable.measure(
                    constraints.copy(
                        maxHeight = itemHeight.dp.roundToPx()
                    )
                )
                layout(
                    placeable.width,
                    placeable.height
                ) {
                    placeable.place(0, 0)
                }
            }
            .background(color = Color.Blue)
        )
        Text(
            text = "This is text filling the rest of the row",
            modifier = Modifier
                .weight(1f)
                .padding(8.dp)
        )
    }
}

Solution

  • Constraints is a range to measure Composables as Measurables. If minheight is not set it comes from parent or previous size Constraints. If it's 0, your Composable gets measured between 0 and pixel value of 256.dp, because of that you need to set minHeight to same value of as maxHeight for it to be measured with fixed height instead of in a range. Let's say, if content is 30.dp and it gets measured with 0-100.dp range it never assigned with 100.dp height.

    Also setting placeable.place(0, yPos) and yPos lower than 0 will let it be above it's defined position in Row.

    And finally if you don't assign hight to parent Row it will get height of child with max height which Box with layout Modifier.

    val itemWidth = 128
    val itemHeight = 256
    
    @Preview
    @Composable
    fun LayoutTest() {
        Column(
            modifier = Modifier.fillMaxSize()
        ) {
            Row(
                modifier = Modifier
                    .padding(top = 300.dp)
                    .fillMaxWidth()
                    .height(50.dp)
                    .border(2.dp, Color.Red)
            ) {
                Box(
                    modifier = Modifier
                        .width(itemWidth.dp)
                        .layout { measurable, constraints ->
                            val placeable = measurable.measure(
                                constraints.copy(
                                    minHeight = itemHeight.dp.roundToPx(),
                                    maxHeight = itemHeight.dp.roundToPx()
                                )
                            )
                            layout(
                                placeable.width,
                                placeable.height
                            ) {
                                placeable.placeRelative(0, 0)
                            }
                        }
                        .background(color = Color.Blue)
                        .border(2.dp, Color.Green)
    
                )
                Text(
                    text = "This is text filling the rest of the row",
                    modifier = Modifier
                        .weight(1f)
                        .padding(8.dp)
                )
            }
        }
    }
    

    Results like this

    enter image description here

    The reason it's blue box is centered is it doesn't abide parent Constraints because of that parent tries to center it. You can refer this answer for more details but in general if measurement is not in range of Constraints from parent or previous Modifier constraints composable is centered whether it's bigger or smaller.

    And to overcome this you need to offset as as difference of two with

    val userOffset = 0
    placeable.placeRelative(
        x = 0,
        y = (constraints.maxHeight - placeable.height) / 2 + userOffset
    )
    

    Will result

    enter image description here

    If you set negative values for userOffset blue box will be placed closer to top.