Skip to content

Instantly share code, notes, and snippets.

@mxalbert1996
Created April 6, 2023 07:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mxalbert1996/0f0fdebd640a4f22a0125a88f4cf2eab to your computer and use it in GitHub Desktop.
Save mxalbert1996/0f0fdebd640a4f22a0125a88f4cf2eab to your computer and use it in GitHub Desktop.
Auto Size (Auto Shrink) Text
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.node.Ref
import androidx.compose.ui.semantics.getTextLayoutResult
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.text
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.Paragraph
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
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.rememberTextMeasurer
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.constrain
import androidx.compose.ui.unit.isSpecified
/**
* A component similar to [Text] but can automatically shrink the text to fit the constraints
* if the text is too big.
*
* Note that this component doesn't support text selection through [SelectionContainer].
*
* @param text The text to be displayed.
* @param modifier [Modifier] to apply to this layout node.
* @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set,
* this will be [LocalContentColor].
* @param fontSize The size of glyphs to use when painting the text. See [TextStyle.fontSize].
* @param minFontSize The minimum font size to shrink the text to. [TextUnit.Unspecified] means
* no minimum font size.
* @param autoShrinkMultiplier The number to multiply the font size by when shrinking the text.
* Must be between 0 and 1 (exclusive).
* @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
* See [TextStyle.fontStyle].
* @param fontWeight The typeface thickness to use when painting the text (e.g., [FontWeight.Bold]).
* @param fontFamily The font family to be used when rendering the text. See [TextStyle.fontFamily].
* @param letterSpacing The amount of space to add between each letter.
* See [TextStyle.letterSpacing].
* @param textDecoration The decorations to paint on the text (e.g., an underline).
* See [TextStyle.textDecoration].
* @param textAlign The alignment of the text within the lines of the paragraph.
* See [TextStyle.textAlign].
* @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
* See [TextStyle.lineHeight].
* @param overflow How visual overflow should be handled.
* @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
* text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
* [overflow] and TextAlign may have unexpected effects.
* @param maxLines An optional maximum number of lines for the text to span, wrapping if
* necessary. If the text exceeds the given number of lines, it will be truncated according to
* [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
* @param onTextLayout Callback that is executed when a new text layout is calculated. A
* [TextLayoutResult] object that callback provides contains paragraph information, size of the
* text, baselines and other details. The callback can be used to add additional decoration or
* functionality to the text. For example, to draw selection around the text.
* @param style Style configuration for the text such as color, font, line height etc.
*/
@OptIn(ExperimentalTextApi::class)
@Composable
fun AutoShrinkText(
text: AnnotatedString,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
minFontSize: TextUnit = TextUnit.Unspecified,
autoShrinkMultiplier: Float = 0.9f,
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
) {
val textColor = color.takeOrElse {
style.color.takeOrElse {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
}
}
val mergedStyle = style.merge(
TextStyle(
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight,
textAlign = textAlign,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
fontStyle = fontStyle,
letterSpacing = letterSpacing
)
)
val textMeasurer = rememberTextMeasurer()
val textLayoutResult = remember { Ref<TextLayoutResult>() }
Layout(
modifier = modifier
.graphicsLayer()
.drawBehind {
textLayoutResult.value?.let {
drawText(it)
}
}
.semantics {
this.text = text
getTextLayoutResult { textLayoutResults ->
textLayoutResult.value?.let {
textLayoutResults.add(it)
true
} ?: false
}
}
) { _, constraints ->
var result: TextLayoutResult
var actualStyle = mergedStyle
while (true) {
result = textMeasurer.measure(
text, actualStyle, overflow, softWrap, maxLines, emptyList(), constraints
)
if (result.hasVisualOverflow) {
var nextFontSize = actualStyle.fontSize * autoShrinkMultiplier
if (minFontSize.isSpecified) {
if (actualStyle.fontSize == minFontSize) break
if (nextFontSize < minFontSize) {
nextFontSize = minFontSize
}
}
actualStyle = actualStyle.copy(fontSize = nextFontSize)
} else {
break
}
}
onTextLayout(result)
textLayoutResult.value = result
val (width, height) = constraints.constrain(result.size)
layout(width, height) {}
}
}
/**
* A component similar to [Text] but can automatically shrink the text to fit the constraints
* if the text is too big.
*
* Note that this component doesn't support text selection through [SelectionContainer].
*
* @param text The text to be displayed.
* @param modifier [Modifier] to apply to this layout node.
* @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set,
* this will be [LocalContentColor].
* @param fontSize The size of glyphs to use when painting the text. See [TextStyle.fontSize].
* @param minFontSize The minimum font size to shrink the text to.
* @param autoSizeMultiplier The number to multiply the font size by when shrinking the text.
* Must be between 0 and 1 (exclusive).
* @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
* See [TextStyle.fontStyle].
* @param fontWeight The typeface thickness to use when painting the text (e.g., [FontWeight.Bold]).
* @param fontFamily The font family to be used when rendering the text. See [TextStyle.fontFamily].
* @param letterSpacing The amount of space to add between each letter.
* See [TextStyle.letterSpacing].
* @param textDecoration The decorations to paint on the text (e.g., an underline).
* See [TextStyle.textDecoration].
* @param textAlign The alignment of the text within the lines of the paragraph.
* See [TextStyle.textAlign].
* @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
* See [TextStyle.lineHeight].
* @param overflow How visual overflow should be handled.
* @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
* text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
* [overflow] and TextAlign may have unexpected effects.
* @param maxLines An optional maximum number of lines for the text to span, wrapping if
* necessary. If the text exceeds the given number of lines, it will be truncated according to
* [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
* @param onTextLayout Callback that is executed when a new text layout is calculated. A
* [TextLayoutResult] object that callback provides contains paragraph information, size of the
* text, baselines and other details. The callback can be used to add additional decoration or
* functionality to the text. For example, to draw selection around the text.
* @param style Style configuration for the text such as color, font, line height etc.
*/
@Composable
fun AutoShrinkText(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
minFontSize: TextUnit = TextUnit.Unspecified,
autoSizeMultiplier: Float = 0.9f,
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
) {
AutoShrinkText(
AnnotatedString(text),
modifier,
color,
fontSize,
minFontSize,
autoSizeMultiplier,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
overflow,
softWrap,
maxLines,
onTextLayout,
style
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment