Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specify minimal lines for Text in Jetpack Compose

For various reasons a Text should always have at least the height equal to x lines of text, no matter if it has less than x lines of text. The Text and BasicText Composables only have a maxLines parameter but no minLines

I have tried the following (x = 3):

Text(
    modifier = Modifier.sizeIn(minHeight = with(LocalDensity.current) {
       (42*3).sp.toDp()
    }),
    color = MaterialTheme.colors.onPrimary,
    text = "Sample", textAlign = TextAlign.Center,
    style = MaterialTheme.typography.h2 /* fontSize = 42 */,
    lineHeight = 42.sp
)

The resulting height is less than if the text would contain 3 lines

Back in View World Android, we could simply use minLines=3, how can we achieve this in Jetpack Compose?

like image 661
Yannick Avatar asked Feb 27 '21 01:02

Yannick


People also ask

How do you change Text size in compose?

To change font size of Text composable in Android Jetpack Compose, pass a required font size value for the optional fontSize parameter of Text composable. Make sure to import sp , as shown in the above code snippet, when you are assign fontSize property with scale-independent pixels.

How do I get TextField value in jetpack compose?

To read value entered in TextField in Android Compose, declare a variable, and assign this variable the value in the TextField whenever there is a change in the value. The same process holds for reading value entered in OutlineTextField composable.


Video Answer


2 Answers

Your code is almost correct, just set lineHeight to fontSize*4/3:

var lineHeight = MaterialTheme.typography.h2.fontSize*4/3

Text(
    modifier = Modifier.sizeIn(minHeight = with(LocalDensity.current) {
       (lineHeight*3).toDp()
    }),
    color = MaterialTheme.colors.onPrimary,
    text = "Sample", textAlign = TextAlign.Center,
    style = MaterialTheme.typography.h2,
    lineHeight = lineHeight
)

But you can do something similar without calculations using onTextLayout callback:

fun main() = Window {
    var text by remember { mutableStateOf("Hello, World!") }
    var lines by remember { mutableStateOf(0) }

    MaterialTheme {
        Button(onClick = {
            text += "\nnew line"
        }) {
            Column {
                Text(text,
                    maxLines = 5,
                    style = MaterialTheme.typography.h2,
                    onTextLayout = { res -> lines = res.lineCount })
                for (i in lines..2) {
                    Text(" ", style = MaterialTheme.typography.h2)
                }
            }
        }
    }
}
like image 167
Spatz Avatar answered Oct 11 '22 13:10

Spatz


While we are waiting for Google implements this feature you can use this workaround:

@Preview
@Composable
fun MinLinesPreview() {
    lateinit var textLayoutResult: TextLayoutResult

    val text = "Title\ntitle\nTITLE\nTitle"
//    val text = "title\ntitle\ntitle\ntitle"
//    val text = "title\ntitle"
//    val text = "title"

    Text(
        modifier = Modifier.fillMaxWidth(),
        text = text.addEmptyLines(3), // ensures string has at least N lines,
        textAlign = TextAlign.Center,
        maxLines = 4,
    )
}

fun String.addEmptyLines(lines: Int) = this + "\n".repeat(lines)

Now your Text has the same height regardless string content:

Text height with 1 line Text height with 2 lines Text height with 4 lines Text height with 4 lines with capslock

This solution is much more easier than calculate Text's bottom offset based on line height in onTextLayout (spoiler: start, center and last line have different height)

like image 2
RareScrap Avatar answered Oct 11 '22 13:10

RareScrap