I’m trying to create some UI in compose which I ca...
# compose
s
I’m trying to create some UI in compose which I can’t get my head around how to do it, more in thread. 🧵
I want to have a card where basically I want some text centered at the card, and a small text at the top which sits there but doesn’t change how the text is centered in the card. Current code something like this:
Copy code
Card(...) {
  Column(...) {
    Badge()
    Text(padding(8.dp))
  }
}
And I can imitate what I am going for this doing something like
Copy code
Card(...) {
  Column(...) {
    Badge()
    Text(padding(8.dp))
    CompositionLocalProvider(LocalContentAlpha provides 0f) {
      // This has the exact same height making the Text() look centered
      Badge()
    }
  }
}
Which feels super hacky, especially considering I am inflating Badge again for no reason just to get the exact same space at the bottom too. First pic shows what it looks like, second how I want it to look like, but preferably without the extra Badge() hack Any ideas on this? I’ve tried going with a Box() too but that doesn’t really help either since it lets the children overlap which isn’t what I am going for
Could also “easily” do this with a custom
layout {}
where I can measure the badge and then just add a spacer at the bottom equal to the height it would take, but this feels like extra extra for what I am going for
Fixed it with this for now: (Always happy to get feedback using
Layout{}
since I don’t feel super comfortable with it so I may be doing things wrong!)
Copy code
@Composable
fun CenteredTextWithBadge(
    centeredText: @Composable (modifier: Modifier) -> Unit,
    badge: (@Composable (modifier: Modifier) -> Unit)? = {},
) {
    Layout(
        content = {
            centeredText(Modifier.layoutId("centeredText"))
            badge?.invoke(Modifier.layoutId("badge"))
        },
    ) { measurables, constraints ->
        val textPlaceable = measurables.first { it.layoutId == "centeredText" }.measure(constraints)
        val badgePlaceable = measurables.firstOrNull { it.layoutId == "badge" }?.measure(constraints)

        val textHeight = textPlaceable.height
        val badgeHeight = badgePlaceable?.height ?: 0

        val maxWidth = constraints.maxWidth
        val layoutHeight = textHeight + (badgeHeight * 2)

        layout(maxWidth, layoutHeight) {
            badgePlaceable?.place(
                x = (maxWidth - badgePlaceable.width) / 2,
                y = 0,
            )
            textPlaceable.place(
                x = (maxWidth - textPlaceable.width) / 2,
                y = badgeHeight,
            )
        }
    }
}
Where the call site can be something like:
Copy code
CenteredTextWithBadge(
    centeredText = { modifier ->
        Text(
            text = text,
            style = MaterialTheme.typography.subtitle2,
            textAlign = TextAlign.Center,
            modifier = modifier.padding(8.dp)
        )
    },
    badge = if (badge != null) {
        { modifier ->
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text(
                    text = badge,
                    style = MaterialTheme.typography.caption,
                    textAlign = TextAlign.Center,
                    modifier = modifier
                )
            }
        }
    } else {
        null
    }
)
Where
text: String, badge: String?
t
Can you use a
Box
? You can center the each align within
Box
independently, and then put the
Box
in the
Card
s
Yeah but then that doesn’t change when the height of
badge
changes. So if
Badge
becomes super tall for whatever reason they will overlap
z
Sounds like a good use case for constraint Layout too
t
If that space at the bottom is always a function of the height of
badge
then a custom layout makes sense because you’re programmatically ensuring the two always change in tandem