29

I'm struggling with vertically centering text in Jetpack Compose version alpha-11. It appears that my font has a significant amount of padding and I'm unable to find a way to disable it. This has come up only once before on SO, as far as I can tell, here, but their answer of using a constraint layout seems to suggest that they simply positioned it absolutely, which isn't exactly a solution as much as a workaround, and something I'd like to avoid.

You can see it clearly in the screenshot below.

enter image description here

The code for that looks like this:

                   Column(verticalArrangement = Arrangement.Center) {
                        Text(
                            text = "Let's Go",
                            color = Color.White,
                            fontSize = 120.sp,
                            fontFamily = oswaldLightFontFamily(),
                            textAlign = TextAlign.Center,
                            modifier = Modifier.background(Color.Blue)
                        )
                    }

The arguments you would expect to position it -- verticalArrangement and textAlign -- do not do anything here but I'm including them to demonstrate what I've tried.

My workaround so far has been to use Modifier.graphicsLayer(translationY = -25f) to shift it up but that seems like a terrible hack for something that should be so straightforward. It appears that in classic Android layouts, one could set android:includeFontPadding="false" and that would bypass this behavior but there doesn't seem to be a similar option in Jetpack Compose.

Anyone encounter this?

4
  • Does the -25f work well on all devices for this font or there needs to be a density multiplier to the number for it to work "similarly" on all devices ? Commented Feb 27, 2021 at 22:55
  • Tbh I haven’t tried it on other devices, I’m spoiled by only having to target one particular device with a specific resolution. I would expect to need to tweak it so it works correctly on other devices, though. I’m not experienced enough with Android to advise there, this is just the way I’d handle something like this in CSS. Also note that the -25f thing is usually just a starting point, I’ve been fine tuning as necessary. I’d also expect other fonts to be different. It all feels so hacky, I hope there’s a better solution soon! Commented Feb 28, 2021 at 13:59
  • 2
    issuetracker.google.com/issues/171394808 Commented Apr 29, 2021 at 12:05
  • issuetracker.google.com/issues/171394808#comment24 That workaround works pretty pretty good. Commented Feb 13, 2022 at 6:54

6 Answers 6

24

This happens due to uneven font padding on https://fonts.google.com/specimen/Oswald, plus the text you're using in lowercase makes the discrepancy more obvious.

As @Siyamed mentioned below, the API to turn the default includeFontPadding behaviour off in Compose was released with Compose 1.2 beta and you use it like so:

Text(
...
   style = TextStyle(
      platformStyle = PlatformTextStyle(
     includeFontPadding = false
   ),
)

https://android-developers.googleblog.com/2022/05/whats-new-in-jetpack-compose.html

give it a try, might help? Btw, if you notice PlatformTextStyle is "deprecated" this only wants to inform that this is a compatibility API. We removed the deprecation warning in more recent compose versions, from 1.5 and above.

Sign up to request clarification or add additional context in comments.

Comments

8

According to https://issuetracker.google.com/issues/171394808, It seems this is one of the limitations of the current JetPack Compose.

This is also deal breaker for my app because the font used rely heavily with the includeFontPadding. For current workaround, I create a CoreText that wraps TextView inside my compose.

Here's example of my wrapper, its not perfect but it does the job for my current use case:

@Composable
fun CoreText(
    text: String,
    modifier: Modifier = Modifier,
    color: Color = Color.Unspecified,
    textDecoration: TextDecoration? = null,
    textAlign: TextAlign? = null,
    overflow: TextOverflow = TextOverflow.Clip,
    maxLines: Int = Int.MAX_VALUE,
    style: TextStyle = Typography.body2,
    onClick: (() -> Unit)? = null,
) {
    AndroidView(
        modifier = modifier,
        factory = { context ->
            TextView(context)
        },
        update = {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                it.setTextAppearance(style.fontWeight.toStyle())
            } else {
                it.setTextAppearance(it.context, style.fontWeight.toStyle())
            }

            if (overflow == TextOverflow.Ellipsis) {
                it.ellipsize = TextUtils.TruncateAt.END
            }

            if (textDecoration != null) {
                it.paintFlags = when (textDecoration) {
                    TextDecoration.Underline -> {
                        Paint.UNDERLINE_TEXT_FLAG
                    }
                    TextDecoration.LineThrough -> {
                        Paint.STRIKE_THRU_TEXT_FLAG
                    }
                    else -> 0
                }
            }

            if (onClick != null) {
                it.setOnClickListener { onClick.invoke() }
            }

            if (color != Color.Unspecified || style.color != Color.Unspecified) {
                it.setTextColor(if (color == Color.Unspecified) style.color.toArgb() else color.toArgb())
            }

            it.textSize = style.fontSize.value
            it.text = text
            it.background = ColorDrawable(style.background.toArgb())
            it.maxLines = maxLines
            it.includeFontPadding = false
            it.textAlignment = textAlign?.toStyle() ?: style.textAlign.toStyle()
        }
    )
}

// Replace with your style
fun FontWeight?.toStyle(): Int {
    return when (this) {
        FontWeight.Bold -> R.style.TextStyle_Bold
        FontWeight.Normal -> R.style.TextStyle_Regular
        FontWeight.Medium, FontWeight.SemiBold -> R.style.TextStyle_Medium
        else -> -1
    }
}

fun TextAlign?.toStyle(): Int {
    return when (this) {
        TextAlign.Left -> TEXT_ALIGNMENT_TEXT_START
        TextAlign.Right -> TEXT_ALIGNMENT_TEXT_END
        TextAlign.Center -> TEXT_ALIGNMENT_CENTER
        TextAlign.Start -> TEXT_ALIGNMENT_TEXT_START
        TextAlign.End -> TEXT_ALIGNMENT_TEXT_END
        else -> -1
    }
}

1 Comment

This has been fixed in Compose 1.2.0-alpha05! See last bullet under developer.android.com/jetpack/androidx/releases/…
3

By using Compose 1.2.0-alpha07 and above, you can use PlatformTextStyle api to set includeFontPadding.

Try to the below code:

private val NotoSans = FontFamily(
    Font(R.font.noto_san_jp_black, FontWeight.Black),
    Font(R.font.noto_san_jp_light, FontWeight.Light),
    Font(R.font.noto_san_jp_bold, FontWeight.Bold),
    Font(R.font.noto_san_jp_thin, FontWeight.Thin),
    Font(R.font.noto_san_jp_medium, FontWeight.Medium),
    Font(R.font.noto_san_jp_regular, FontWeight.Normal),
)

val Typography = Typography(
    headlineLarge = Typography().headlineLarge.copy(
        fontFamily = NotoSans,
    )
)

@OptIn(ExperimentalTextApi::class)
/* ... */

Text(
    text = "地域のお得は\nすべてここに",
    style = MaterialTheme.typography.headlineLarge.copy(
        platformStyle = PlatformTextStyle(
            includeFontPadding = false
        )
    /* ... */
    )
)

The result when includeFontPadding = false:

includeFontPadding = false

The result when includeFontPadding = true or no using it:

includeFontPadding = true or not using

More information:

Fixing Font Padding in Compose Text - Medium

Comments

1

Compose now have TextStyle.platformStyle.incudeFontPadding that is set to true by default for version 1.2. you can set it to false in your TextStyle

Making the default false is something that Compose wants to do in v1.3 or later.

Comments

-1

(Temporary) custom solution:

fun Modifier.baselinePadding(
    firstBaselineToTop: Dp,
    lastBaselineToBottom: Dp
) = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)

    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    check(placeable[LastBaseline] != AlignmentLine.Unspecified)
    val lastBaseline = placeable[LastBaseline]

    val lastBaselineToBottomHeight = placeable.height - lastBaseline

    val lastBaselineToBottomDelta = lastBaselineToBottom.roundToPx() - lastBaselineToBottomHeight

    val totalHeight = placeable.height +
            (firstBaselineToTop.roundToPx() - firstBaseline)

    val placeableY = totalHeight - placeable.height
    layout(placeable.width, totalHeight + lastBaselineToBottomDelta) {
        placeable.placeRelative(0, placeableY)
    }
}

1 Comment

How does this let you center the text? It looks like it just lets you set padding to top/bottom baseline, but there's no way to know how much padding to use to center the text visually
-3

Just got around this same issue.

Box(contentAlignment = Alignment.Center){
       Text(
                text = "OK"
                textAlign = TextAlign.Center
        )
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.