Having some issues get things to look right with T...
# compose
s
Having some issues get things to look right with Text(). When the Text uses
.wrapContentWidth()
(red screenshot), it fills more than the width it needs after it wrapped the text to 2 lines. I want it to shrink down to the size it actually needs. I can simulate this with a hardcoded
.width(140.dp)
(blue screenshot). How would I achieve this without hardcoding?
👀 1
c
You may have to use
AnnotatedString
and change background in the
SpanStyle
. Otherwise, setting background modifier on Text won’t work since it applies it to the whole container. @Halil Ozercan might know better though
s
The problem isn't the background, it's the spacing. I want the text to be centered in it's parent, but I still want to use TextAlign.Start
blue/red backgrounds are just to show the bounds of Text for dev/debugging
h
If you really want to know the bounds of Text, those background colors actually showing the full extend of the Text composable. There is an open issue about this b/206039942. Text layout happens kind of independent from overall Compose layout hierarchy. It is delegated to StaticLayout from platform.
wrapContentWidth
instructs Text composable to not care about incoming minimum width constraint. This doesn't do much in terms of wrapping the text horizontally. StaticLayout still wants to use all the width that's available. From what I see,
wrap_second_line
wraps on its own because it cannot fit in the first line. As far as the overall size of the underyling StaticLayout goes, text still spans the whole width. What you can do (after losing the initial frame), take the TextLayoutResult via
onTextLayout
, find the width of every single line using
lineCount
and
getLineWidth
, after measuring the desired width and height for your background, use
Modifier.drawBehind
to draw a rectangle as your background.
s
I think what OP is trying to do here isn't to get the background like this, they mentioned it's just for debug purposes. The point is to make the text feel like it's at the center of the available space, while not using TextAlign.Center which would change how the text itself is laid out.
s
onTextLayout
, find the width of every single line using
lineCount
and
getLineWidth
I might do something like this. There's no getLineWidth, but I guess
getLineEnd() - getLineStart()
would accomplish that. EDIT: Left/Right are probably what I want and not Start/End
ok, I got it. Thanks for the pointer @Halil Ozercan. That led me to TextMeasurer, which I used to avoid the first-frame problem. I don't know if this is optimal, but it gets the job done without flashing a different size on screen. (also, naming is hard, "pre measured text" isn't exactly right, but 🤷 )
min-width-text.kt
h
TextMeasurer is the right way to go. However,
BoxWithConstraints
uses SubcomposeLayout which may introduce some performance drawbacks. Also, I would just suggest you to
remember
the result of text measurement so it's not calculated in every recomposition.
t
If I understand you correctly, you were on the right path already with
.wrapContentWidth()
. I tried the following on desktop:
Copy code
Box(
    modifier = Modifier
        .background(Color.Blue)
        .padding(8.dp)
) {
    Text(
        text = "This is my text\nwith a second, longer line",
        modifier = Modifier
            .fillMaxWidth()
            .background(Color.Gray)
            .wrapContentWidth(Alignment.CenterHorizontally)
            .background(Color.Red),
        textAlign = TextAlign.Start
    )
}
Which yields the attached image. No need for fancy manual text measuring.
s
This has a
\n
in the text itself tough. The point was for the text to measure by itself without such hacks, and then have the result be laid out in the center.
t
Ah, my bad...
s
Regarding BoxWithConstraints Halil, how would they be able to provide the correct constraints to the TextMesurer without it?
h
Wrapping the Text composable with a Layout composable should do the trick. You can take the original incoming constraints, measure your text in layout phase, then send down fixed constraints for Text composable.
A rule of thumb for me about subcomposition is that if you are using the result of subcomposition to set layout modifiers like
width
,
height
,
size
, it's very likely that a Layout composable can do the same job.
s
Oh interesting, thanks for the tip. I'll try that instead
I would just suggest you to
remember
the result of text measurement so it's not calculated in every recomposition.
I can't use
remember
inside the MeasurePolicy block since it's not a @Composable, but I can't measure outside the
Layout
, because I need the constraints. Is there a way to optimize this? This is what I have so far (first time playing with Layout/measurables/placeables so feedback appreciated)
h
remember
advice was for the first solution. As you said, It wouldn't work when the measurement is done during the layout phase. In this case you can rely on
TextMeasurer
's own internal cache. I think this solution is fine as it is.