Skip to content

Instantly share code, notes, and snippets.

@dovahkiin98
Created October 4, 2021 09:03
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
AutoSize text implementation in Jetpack Compose
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.InternalFoundationTextApi
import androidx.compose.foundation.text.TextDelegate
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFontLoader
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.min
import androidx.compose.ui.unit.sp
@Composable
fun AutoSizeText(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
suggestedFontSizes: List<TextUnit> = emptyList(),
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,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
) {
AutoSizeText(
AnnotatedString(text),
modifier,
color,
suggestedFontSizes,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
overflow,
softWrap,
maxLines,
emptyMap(),
onTextLayout,
style,
)
}
@Composable
fun AutoSizeText(
text: AnnotatedString,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
suggestedFontSizes: List<TextUnit> = emptyList(),
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,
inlineContent: Map<String, InlineTextContent> = mapOf(),
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
) {
BoxWithConstraints(
modifier = modifier,
contentAlignment = Alignment.Center,
) {
var combinedTextStyle = (LocalTextStyle.current + style).copy(
fontSize = min(maxWidth, maxHeight).value.sp
)
val fontSizes = suggestedFontSizes.ifEmpty {
MutableList(combinedTextStyle.fontSize.value.toInt()) {
(combinedTextStyle.fontSize.value - it).sp
}
}
var currentFontIndex = 0
while (shouldShrink(text, combinedTextStyle, maxLines)) {
combinedTextStyle =
combinedTextStyle.copy(fontSize = fontSizes[++currentFontIndex])
}
Text(
text,
Modifier,
color,
TextUnit.Unspecified,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
overflow,
softWrap,
maxLines,
inlineContent,
onTextLayout,
combinedTextStyle,
)
}
}
@OptIn(InternalFoundationTextApi::class)
@Composable
private fun BoxWithConstraintsScope.shouldShrink(
text: AnnotatedString,
textStyle: TextStyle,
maxLines: Int
): Boolean {
val textDelegate = TextDelegate(
text,
textStyle,
maxLines,
true,
TextOverflow.Clip,
LocalDensity.current,
LocalFontLoader.current,
)
val textLayoutResult = textDelegate.layout(
constraints,
LocalLayoutDirection.current,
)
return textLayoutResult.hasVisualOverflow
}
@vurgunmert
Copy link

Line 106 crashes. Replace block

    while (shouldShrink(text, combinedTextStyle, maxLines) && fontSizes.size > ++currentFontIndex) {
        combinedTextStyle =
            combinedTextStyle.copy(fontSize = fontSizes[currentFontIndex])
    }

@Qdafengzi
Copy link

AnnotatedString cannot support ?

@inidamleader
Copy link

inidamleader commented May 19, 2023

This is an improved version:
Features:

  1. Best performance: Utilizes a dichotomous binary search algorithm to quickly find the optimal text size without unnecessary iterations.
  2. Text alignment support: Supports 6 possible alignment values through the Alignment interface.
  3. Material Design 3 support.
  4. Font scaling support: Changing the font scale by the user does not affect the visual rendering result.

Limitation:

  1. Does not work well when maxLine is greater than 1.
  2. Does not work well when changing lineHeight.

Tested on my own app with 800k+ downloads.

@wesjon
Copy link

wesjon commented Aug 4, 2023

Thank you so much for this component. It works great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment