Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

android:autoSizeTextType in Jetpack Compose

Is there a way to adjust the text to always resize depending a fixed height ?

I have a column that has a fixed height and in which the text inside should always fit

 Column(modifier = Modifier.height(150.dp).padding(8.dp)) {
   Text("My really long long long long long text that needs to be resized to the height of this Column")
}
like image 257
SNM Avatar asked Sep 19 '20 17:09

SNM


People also ask

How do I make Text bold in compose?

Android Compose – Change Text to Bold To change font weight of Text composable to Bold, in Android Jetpack Compose, pass FontWeight. Bold for the optional fontWeight parameter of Text composable.

How do I 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.

What is scaffold Android jetpack compose?

A Scaffold is a layout which implements the basic material design layout structure. You can add things like a TopBar, BottomBar, FAB or a Drawer.


3 Answers

I use the following to adjust the font size with respect to the available width:

val textStyleBody1 = MaterialTheme.typography.body1
var textStyle by remember { mutableStateOf(textStyleBody1) }
var readyToDraw by remember { mutableStateOf(false) }
Text(
    text = "long text goes here",
    style = textStyle,
    maxLines = 1,
    softWrap = false,
    modifier = modifier.drawWithContent {
        if (readyToDraw) drawContent()
    },
    onTextLayout = { textLayoutResult ->
        if (textLayoutResult.didOverflowWidth) {
            textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9)
        } else {
            readyToDraw = true
        }
    }
)

To adjust the font size based on height, play around with the Text composable's attributes and use didOverflowHeight instead of didOverflowWidth:

val textStyleBody1 = MaterialTheme.typography.body1
var textStyle by remember { mutableStateOf(textStyleBody1) }
var readyToDraw by remember { mutableStateOf(false) }
Text(
    text = "long text goes here",
    style = textStyle,
    overflow = TextOverflow.Clip,
    modifier = modifier.drawWithContent {
        if (readyToDraw) drawContent()
    },
    onTextLayout = { textLayoutResult ->
        if (textLayoutResult.didOverflowHeight) {
            textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9)
        } else {
            readyToDraw = true
        }
    }
)

In case you need to synchronize the font size across multiple items in a list, save the text style outside of the composable function:

private val textStyle = mutableStateOf(MaterialTheme.typography.body1)

@Composable
fun YourComposable() {
    Text(...)
}

This is certainly not perfect, as it might take some frames until the size fits and the text draws finally.

like image 195
Brian Avatar answered Oct 18 '22 18:10

Brian


This is a composable based on @Brian and @zxon comments to autosize the Text based on the available width.

@Composable
fun AutoSizeText(
    text: String,
    textStyle: TextStyle,
    modifier: Modifier = Modifier
) {
    var scaledTextStyle by remember { mutableStateOf(textStyle) }
    var readyToDraw by remember { mutableStateOf(false) }

    Text(
            text,
            modifier.drawWithContent {
                if (readyToDraw) {
                    drawContent()
                }
            },
            style = scaledTextStyle,
            softWrap = false,
            onTextLayout = { textLayoutResult ->
                if (textLayoutResult.didOverflowWidth) {
                    scaledTextStyle =
                            scaledTextStyle.copy(fontSize = scaledTextStyle.fontSize * 0.9)
                } else {
                    readyToDraw = true
                }
            }
    )
}

The preview doesn't work correctly with this (at least with beta09), you can add this code to use a placeholder for the preview:

    if (LocalInspectionMode.current) {
        Text(
                text,
                modifier,
                style = textStyle
        )
        return
    } 
like image 17
Nieto Avatar answered Oct 18 '22 20:10

Nieto


I built on top of Brian's answer to support other properties of Text which are also hoisted and can be used by the caller.

@Composable
fun AutoResizeText(
    text: String,
    fontSizeRange: FontSizeRange,
    modifier: Modifier = Modifier,
    color: Color = Color.Unspecified,
    fontStyle: FontStyle? = null,
    fontWeight: FontWeight? = null,
    fontFamily: FontFamily? = null,
    letterSpacing: TextUnit = TextUnit.Unspecified,
    textDecoration: TextDecoration? = null,
    textAlign: TextAlign? = null,
    lineHeight: TextUnit = TextUnit.Unspecified,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    style: TextStyle = LocalTextStyle.current,
) {
    var fontSizeValue by remember { mutableStateOf(fontSizeRange.max.value) }
    var readyToDraw by remember { mutableStateOf(false) }

    Text(
        text = text,
        color = color,
        maxLines = maxLines,
        fontStyle = fontStyle,
        fontWeight = fontWeight,
        fontFamily = fontFamily,
        letterSpacing = letterSpacing,
        textDecoration = textDecoration,
        textAlign = textAlign,
        lineHeight = lineHeight,
        overflow = overflow,
        softWrap = softWrap,
        style = style,
        fontSize = fontSizeValue.sp,
        onTextLayout = {
            Timber.d("onTextLayout")
            if (it.didOverflowHeight && !readyToDraw) {
                Timber.d("Did Overflow height, calculate next font size value")
                val nextFontSizeValue = fontSizeValue - fontSizeRange.step.value
                if (nextFontSizeValue <= fontSizeRange.min.value) {
                    // Reached minimum, set minimum font size and it's readToDraw
                    fontSizeValue = fontSizeRange.min.value
                    readyToDraw = true
                } else {
                    // Text doesn't fit yet and haven't reached minimum text range, keep decreasing
                    fontSizeValue = nextFontSizeValue
                }
            } else {
                // Text fits before reaching the minimum, it's readyToDraw
                readyToDraw = true
            }
        },
        modifier = modifier.drawWithContent { if (readyToDraw) drawContent() }
    )
}

data class FontSizeRange(
    val min: TextUnit,
    val max: TextUnit,
    val step: TextUnit = DEFAULT_TEXT_STEP,
) {
    init {
        require(min < max) { "min should be less than max, $this" }
        require(step.value > 0) { "step should be greater than 0, $this" }
    }

    companion object {
        private val DEFAULT_TEXT_STEP = 1.sp
    }
}

And the usage would look like:

AutoResizeText(
    text = "Your Text",
    maxLines = 3,
    modifier = Modifier.fillMaxWidth(),
    fontSizeRange = FontSizeRange(
        min = 10.sp,
        max = 22.sp,
    ),
    overflow = TextOverflow.Ellipsis,
    style = MaterialTheme.typography.body1,
)

This way I was able to set different maxLines and even have Ellipsis as overflow as the text was just too big to fit into the set lines even with the smallest size we want.

like image 17
Rahul Sainani Avatar answered Oct 18 '22 19:10

Rahul Sainani