Skip to content

Instantly share code, notes, and snippets.

@dovahkiin98
Created October 4, 2021 09:03
Show Gist options
  • Save dovahkiin98/cd4e1f639cc4f6392018e327d0db44d8 to your computer and use it in GitHub Desktop.
Save dovahkiin98/cd4e1f639cc4f6392018e327d0db44d8 to your computer and use it in GitHub Desktop.
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:

  1. Best performance: Utilizes a dichotomous binary search algorithm to quickly find the optimal text size without unnecessary iterations.
  2. 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.
  5. Multiline Support with maxLines Parameter.

Tested on an app with 900k+ downloads.

@wesjon
Copy link

wesjon commented Aug 4, 2023

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

@LeonardMaetzner
Copy link

LeonardMaetzner commented May 8, 2024

The TextDelegate of androidx.compose.foundation is marked as internal as of version 1.7.0-alpha05. Therefore, this solution can't be used without an unsightly @file:Suppress hack

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