I’m working with a `Row` that renders itself like...
# compose
b
I’m working with a
Row
that renders itself like the image here. In the Row, the red/blue box should fill the height of the row, but also not affect the measured height. Meaning, the other content should determine the height of the row, and then the red/blue box should fill that height. I was using
InstrinsicSize.Min
to accomplish this. But alas, the row content has gotten complex enough that I need to use a
SubComposeLayout
in it (a
BoxWithConstraints
), and well, Intrinsics and SubComposeLayout don’t play nice together. I had been working on this before, and @Zach Klippenstein (he/him) [MOD] had helped me in another thread (https://kotlinlang.slack.com/archives/CJLTWPH7S/p1621712795136000), suggesting that I use a Custom Layout for the Row composable. I’m finally getting a chance to work on that. The code I’ve come up with as a starting point for my custom Layout is included in this thread. I was hoping to get some feedback to see if I’m on the right track with it, since it’s my first attempt at a custom layout….
Here’s the code I have so far for my Custom Row (
MyRow
) … Does this seem like the right approach:
Copy code
private val BOX_WIDTH = 64.dp

@Composable
fun MyRow(
    modifier: Modifier = Modifier,
    content: @Composable ()->Unit
) {

    Layout(
        modifier = modifier,
        content = {
            Box(modifier = Modifier.background(Color.Blue.copy(alpha = 0.6F))) {}
            content()
        }
    ) { measurables, constraints ->

        val boxWidth =  BOX_WIDTH.toPx().toInt()
        val contentWidth = constraints.maxWidth - boxWidth

        val contentPlaceable = measurables[1].measure(constraints.copy(maxWidth = contentWidth, minWidth = contentWidth))

        val boxConstraints = constraints.copy(
            minWidth = boxWidth,
            maxWidth = boxWidth,
            minHeight = contentPlaceable.height,
            maxHeight = contentPlaceable.height)

        val boxPlaceable = measurables[0].measure(boxConstraints)

        layout(constraints.maxWidth, contentPlaceable.height) {
            boxPlaceable.placeRelative(x = 0, y = 0)
            contentPlaceable.placeRelative(x = boxPlaceable.width, y=0)
        }
    }
}
If I call it like this:
Copy code
MyRow(modifier = Modifier.fillMaxWidth()) {
        Column() {
            Text("Row Content", fontSize = 24.sp, fontWeight = FontWeight.Bold)
            Text("The content is dynamic, so it's height is not a constant")
            Text("Sometimes, it could be pretty short")
            Text("Other times, it could be taller")
        }
    }
I get output like this:
z
Generally yea, although you might want to use the sum of box and content widths as the width argument
layout
in case your content ends up not using the whole width. I think you also want to use the
offset
function instead of copying the constraints which I believe will noop if the constraints are unconstrained (infinity-8 is still infinity).
b
Thanks @Zach Klippenstein (he/him) [MOD]! Good to know i’m on the right path. I’ll take a look at
offset
z
To be clear I meant the
Constraints.offset
function
b
yep that’s what I was just looking at.
I might find the
constrainWidth
function useful as well to make sure everything stays within the bounds of the parent constraints.
OK, here’s an updated composable. This seems pretty good to me …
Copy code
private val BOX_WIDTH = 64.dp

@Composable
fun MyRow(
    modifier: Modifier = Modifier,
    content: @Composable ()->Unit
) {

    Layout(
        modifier = modifier,
        content = {
            Box(modifier = Modifier.background(Color.Blue.copy(alpha = 0.6F))) {}
            content()
        }
    ) { measurables, constraints ->

        // Set the width of the box, making sure it's within the parent constraints
        val boxWidth =  constraints.constrainWidth(BOX_WIDTH.toPx().toInt())

        // set the constraints for the content by offsetting
        // the parent constraints by the width of the box.
        // Then measure the content.
        val contentConstraints = constraints.offset(horizontal = -boxWidth)
        val contentPlaceable = measurables[1].measure(contentConstraints)

        // Setup custom constraints for the box,
        // fixing it's width to the value we want,
        // and fixing it's height to match that of the content.
        // Then measure the box.
        val boxConstraints = Constraints(
            minWidth = boxWidth,
            maxWidth = boxWidth,
            minHeight = contentPlaceable.height,
            maxHeight = contentPlaceable.height)
        val boxPlaceable = measurables[0].measure(boxConstraints)

        // Set the width of the layout to the sum of the box and content, and make sure it fits in the
        // parent constraints.
        // The layout height will be the same as the content.
        val layoutWidth = constraints.constrainWidth(boxPlaceable.width + contentPlaceable.width)
        layout(layoutWidth, contentPlaceable.height) {
            // Place both items in the layout.
            boxPlaceable.placeRelative(x = 0, y = 0)
            contentPlaceable.placeRelative(x = boxPlaceable.width, y=0)
        }
    }
}
z
I believe there's a Box overload that doesn't take a function, so you don't need to specify an empty lambda.