androidandroid-jetpack-composeandroid-jetpackandroid-compose-textfield

How can I restrict font size scaling with Jetpack compose?


Trying to understand how to limit font scaling, from this example here:

Text(
  text = "This is resizing text with font scale ${LocalDensity.current.fontScale}",
  fontSize = 20.sp
)

On Android I can set a systemwide font scaling in the systems Accessibility settings between 1.0 (=100%) and 2.0 (=200%)..

I try to create a function to limit the scaling:

@Composable
fun Float.limitFontScale(maxScale: Int = 200): TextUnit {
    val fontScale = LocalDensity.current.fontScale
    val fontScaleLimited = fontScale.coerceAtMost(maxScale.toFloat() / 100f)
    val scaledSp = (this * fontScaleLimited).sp
    return scaledSp
}

When the systems setting is set to 2.0 (=200%), I would like to shrink the scaling to 150% anyway. I have in my code:

Text(
  text = "This is resizing text with font scale ${LocalDensity.current.fontScale}",
  fontSize = 20.sp.value.limitFontScale(150)
)

i was hoping that the outcome of Text() composable would be the same for following adb commands in terminal:

adb shell settings put system font_scale 2.00

adb shell settings put system font_scale 1.50

But unfortunately, the visible outcome for 2.00 is visibly bigger. I way hoping that both have the same size on device?!

What is wrong? The calcualtion in limitFontScale() ?


Solution

  • I think the problem is in this line

    val scaledSp = (this * fontScaleLimited).sp

    it should be

    val scaledSp = (this.value/fontScale * fontScaleLimited).sp

    First you have to get the original value, then scale it up to what you want.

    Here is the code I used for testing. You can play with it if you need to further modify your function.

    I copied it and modified it from the Katie's GitHub repo (https://github.com/KatieBarnett/Experiments/blob/main/jc-text/src/main/java/dev/katiebarnett/experiments/jctext/components/NonResizingText.kt)

    import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.width
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.platform.LocalDensity
    import androidx.compose.ui.tooling.preview.Preview
    import androidx.compose.ui.unit.TextUnit
    import androidx.compose.ui.unit.dp
    import androidx.compose.ui.unit.sp
    
    
    val standardFontSize = 16.sp
    @Composable
    fun RegularResizingText(
        textToDisplay: String,
        modifier: Modifier = Modifier
    ) {
        Text(
            text = textToDisplay,
            fontSize = standardFontSize,
            modifier = modifier
        )
    }
    
    val TextUnit.nonScaledSp
        @Composable
        get() = (this.value / LocalDensity.current.fontScale).sp
    
    @Composable
    fun TextUnit.limitFontScale(maxScale: Int = 200): TextUnit {
        val fontScale = LocalDensity.current.fontScale
        val fontScaleLimited = fontScale.coerceAtMost(maxScale.toFloat() / 100f)
        val scaledSp = (this.value/fontScale * fontScaleLimited).sp
        return scaledSp
    }
    
    
    @Composable
    fun NonResizingTextSp(
        textToDisplay :String,
        modifier: Modifier = Modifier
    ) {
        Text(
            text = textToDisplay,
            fontSize = standardFontSize.limitFontScale(150),
            modifier = modifier
        )
    }
    
    @Preview(
        name = "large font",
        group = "Font scaling",
        fontScale = 1.5f,
        showBackground = true
    )
    @Preview(
        name = "extra large font",
        group = "Font scaling",
        fontScale = 2f,
        showBackground = true
    )
    annotation class FontScalePreviews
    
    @FontScalePreviews
    @Composable
    fun RegularResizingTextPreview() {
        Box(modifier = Modifier.width(500.dp)) {
            RegularResizingText(textToDisplay = "This is regular resizing text with font scale ${LocalDensity.current.fontScale}")
        }
    }
    
    @FontScalePreviews
    @Composable
    fun NonResizingTextSpPreview() {
        Box(modifier = Modifier.width(500.dp)) {
            NonResizingTextSp(textToDisplay = "This is non-resizing text with font scale ${LocalDensity.current.fontScale}")
        }
    }
    

    enter image description here


    Another option is to wrap the Text composable, or all you layout with

    CompositionLocalProvider(
            LocalDensity provides Density(
                LocalDensity.current.density,
                1f // - we set here default font scale instead of system one
            )
        ){
    // your code here 
    }