Suppose a composable takes two composable args whi...
# compose
m
Suppose a composable takes two composable args which are to be placed into a
Column
composable. How to make sure the second composable is constrained to be the same width as (or not larger than) the first composable arg? UPDATE: I found a simple solution using
ConstraintLayout
(posted in the thread) but is there a better way? UPDATE2: Actually the
ConstraintLayout
didn’t end up working for me, so I wrote a custom layout instead (posted in the thread)
d
pass common modifier ig
m
I guess that would work. Is it standard practice to pass in
Modifier
when using slot API?
d
I am not too experienced but when ever i make a screen we make sure that the modifier is passed even if empty so that if ever needed for any such change we can use it
route to screen to uiComponents
m
I’m still not seeing how if can be done. Suppose the first composable sets its own (passed in) modifier to be a fixed size:
Copy code
@Composable
private fun TestPreview(
    firstItem: @Composable (Modifier) -> Unit,
    secondItem: @Composable (Modifier) -> Unit,
    modifier: Modifier = Modifier,
) {
    Column(modifier = modifier) {
        firstItem(Modifier)
        secondItem(Modifier)
    }
}
d
Copy code
@Composable
private fun TestPreview(
    firstItem: @Composable () -> Unit,
    secondItem: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    modifierForInnerComposable: Modifier = Modifier
) {
    Column(modifier = modifier) {
        firstItem(modifierForInnerComposable )
        secondItem(modifierForInnerComposable )
    }
}

when Calling function

TestPreview(firstItem = {Text(text="My name is")}, secondItem = {Text(text="Devashish Bala")}, modifier = Modifier.width(34.dp), modifierForInnerComposable = Modifier.width(24.dp))
something like this
there could be more efficient solutions too depending on specifications of the problem
m
Here’s my solution using `ConstraintLayout`:
Copy code
@Preview(showBackground = true)
@Composable
fun TopAboveBottomPreview() {
    TopAboveBottom(
        topItem = { modifier ->
            Text(
                text = "first item",
                overflow = TextOverflow.Ellipsis,
                maxLines = 1,
                modifier = modifier.background(Color.Green),
            )

        },
        bottomItem = { modifier ->
            Text(
                text = "second long item",
                overflow = TextOverflow.Ellipsis,
                maxLines = 1,
                modifier = modifier.background(Color.Red),
            )
        },
    )
}

@Composable
private fun TopAboveBottom(
    topItem: @Composable (Modifier) -> Unit,
    bottomItem: @Composable (Modifier) -> Unit,
    modifier: Modifier = Modifier,
) {
    ConstraintLayout(modifier = modifier) {
        val (topItemRef, bottomItemRef) = createRefs()
        topItem(
            Modifier.constrainAs(topItemRef) {
                top.linkTo(parent.top)
            }
        )
        bottomItem(
            Modifier.constrainAs(bottomItemRef) {
                top.linkTo(topItemRef.bottom)
                start.linkTo(topItemRef.start)
                end.linkTo(topItemRef.end)
            },
        )
    }
}
d
Nice one
m
Unfortunately it didn’t work for me because I couldn’t get the child composables to get the correct width (needed for auto-sizing text), so I ended up writing my own custom layout:
Copy code
@Composable
fun LayoutItemsVerticallyWrappingFirstWidth(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit,
) {
    Layout(
        content = content,
        modifier = modifier,
    ) { measurables, constraints ->
        val firstWidth = measurables
            .firstOrNull()
            ?.maxIntrinsicWidth(constraints.maxHeight)
            ?: 0
        val itemConstraints = Constraints(
            minWidth = 0,
            minHeight = 0,
            maxWidth = firstWidth,
            maxHeight = constraints.maxHeight,
        )
        val placeables = measurables.map { measurable ->
            measurable.measure(itemConstraints)
        }
        val height = placeables
            .sumOf(Placeable::height)
            .coerceAtMost(constraints.maxHeight)
        layout(width = firstWidth, height = height) {
            var yOffset = 0
            placeables.forEach { placeable ->
                val xOffset = (firstWidth - placeable.width) / 2
                placeable.place(xOffset, yOffset)
                yOffset += placeable.height
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
private fun LayoutItemsVerticallyPreview() {
    LayoutItemsVerticallyWrappingFirstWidth(
        modifier = Modifier.background(Color.Yellow),
    ) {
        Text(
            text = "first item",
            overflow = TextOverflow.Ellipsis,
            maxLines = 1,
            modifier = Modifier.background(Color.Green),
        )
        Text(
            text = "second longer item",
            overflow = TextOverflow.Ellipsis,
            maxLines = 1,
            modifier = Modifier.background(Color.Red),
        )
    }
}
👍 2