¿Hay alguna manera de ajustar el texto para cambiar siempre el tamaño dependiendo de una altura fija?
Tengo una columna que tiene una altura fija y en la que siempre debe caber el texto de dentro
Column(modifier = Modifier.height(150.dp).padding(8.dp)) { Text("My really long long long long long text that needs to be resized to the height of this Column") }Utilizo lo siguiente para ajustar el tamaño de fuente con respecto al ancho disponible:
val textStyleBody1 = MaterialTheme.typography.body1 var textStyle by remember { mutableStateOf(textStyleBody1) } var readyToDraw by remember { mutableStateOf(false) } Text( text = "long text goes here", style = textStyle, maxLines = 1, softWrap = false, modifier = modifier.drawWithContent { if (readyToDraw) drawContent() }, onTextLayout = { textLayoutResult -> if (textLayoutResult.didOverflowWidth) { textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9) } else { readyToDraw = true } } ) Para ajustar el tamaño de la fuente en función de la altura, juegue con los atributos del Text componible y use didOverflowHeight en lugar de didOverflowWidth :
val textStyleBody1 = MaterialTheme.typography.body1 var textStyle by remember { mutableStateOf(textStyleBody1) } var readyToDraw by remember { mutableStateOf(false) } Text( text = "long text goes here", style = textStyle, overflow = TextOverflow.Clip, modifier = modifier.drawWithContent { if (readyToDraw) drawContent() }, onTextLayout = { textLayoutResult -> if (textLayoutResult.didOverflowHeight) { textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9) } else { readyToDraw = true } } )En caso de que necesite sincronizar el tamaño de fuente en varios elementos de una lista, guarde el estilo de texto fuera de la función componible:
private val textStyle = mutableStateOf(MaterialTheme.typography.body1) @Composable fun YourComposable() { Text(...) }Esto ciertamente no es perfecto, ya que puede tomar algunos cuadros hasta que el tamaño se ajuste y el texto se dibuje finalmente.
Esto se basa en la respuesta de Mohammad.
Tienes que encontrar una mejor manera de calcular el tamaño de fuente utilizando la altura del cuadro y la longitud del mensaje.
@Composable fun Greeting() { var width by remember { mutableStateOf(0) } var height by remember { mutableStateOf(0) } val msg = "My really long long long long long text that needs to be resized to the height of this Column" Column(modifier = Modifier.height(150.dp).padding(8.dp).background(Color.Blue).onPositioned { width = it.size.width height = it.size.height }) { Log.d("mainactivity", "width = $width") Log.d("mainactivity", "height = $height") Text( modifier = Modifier.background(Color.Green).fillMaxHeight(), style = TextStyle(fontSize = calculateFontSize(msg, height).sp), text = msg ) } } fun calculateFontSize(msg: String, height: Int): Int { return height / (msg.length / 5) }Me gustaría agregar que, si no desea ver los estados intermedios de la respuesta de @Brian, puede probar esto.
modifier = Modifier .drawWithContent { if (calculationFinish) { // replace by your logic drawContent() } },Este es un componible basado en los comentarios de @Brian y @zxon para autodimensionar el texto según el ancho disponible.
@Composable fun AutoSizeText( text: String, textStyle: TextStyle, modifier: Modifier = Modifier ) { var scaledTextStyle by remember { mutableStateOf(textStyle) } var readyToDraw by remember { mutableStateOf(false) } Text( text, modifier.drawWithContent { if (readyToDraw) { drawContent() } }, style = scaledTextStyle, softWrap = false, onTextLayout = { textLayoutResult -> if (textLayoutResult.didOverflowWidth) { scaledTextStyle = scaledTextStyle.copy(fontSize = scaledTextStyle.fontSize * 0.9) } else { readyToDraw = true } } ) }La vista previa no funciona correctamente con esto (al menos con beta09), puede agregar este código para usar un marcador de posición para la vista previa:
if (LocalInspectionMode.current) { Text( text, modifier, style = textStyle ) return }Actualización : Esto puede haber dejado de funcionar después de la versión 1.0.1....
Otra forma de hacer esto inspirada en la respuesta de @nieto es cambiar el tamaño sin recomponer simplemente midiendo manualmente usando el bloque de párrafo dadas las restricciones de entrada. También previsualiza correctamente como bonificación.
@Composable fun AutoSizeText( text: String, style: TextStyle, modifier: Modifier = Modifier, minTextSize: TextUnit = TextUnit.Unspecified, maxLines: Int = Int.MAX_VALUE, ) { BoxWithConstraints(modifier) { var combinedTextStyle = LocalTextStyle.current + style while (shouldShrink(text, combinedTextStyle, minTextSize, maxLines)) { combinedTextStyle = combinedTextStyle.copy(fontSize = combinedTextStyle.fontSize * .9f) } Text( text = text, style = style + TextStyle(fontSize = combinedTextStyle.fontSize), maxLines = maxLines, ) } } @Composable private fun BoxWithConstraintsScope.shouldShrink( text: String, combinedTextStyle: TextStyle, minimumTextSize: TextUnit, maxLines: Int ): Boolean = if (minimumTextSize == TextUnit.Unspecified || combinedTextStyle.fontSize > minimumTextSize) { false } else { val paragraph = Paragraph( text = text, style = combinedTextStyle, width = maxWidth.value, maxLines = maxLines, density = LocalDensity.current, resourceLoader = LocalFontLoader.current, ) paragraph.height > maxHeight.value }hice algo como esto
@Composable fun AutosizeText() { var multiplier by remember { mutableStateOf(1f) } Text( "Some long-ish text", maxLines = 1, // modify to fit your need overflow = TextOverflow.Visible, style = LocalTextStyle.current.copy( fontSize = LocalTextStyle.current.fontSize * multiplier ), onTextLayout = { if (it.hasVisualOverflow) { multiplier *= 0.99f // you can tune this constant } } ) }puede ver visualmente que el texto se reduce hasta que encaja
pruebe BoxWithConstraints y aprenda el concepto SubcomposeLayout
BoxWithConstraints( modifier = Modifier .fillMaxWidth() .weight(5f) ) { val size = min(maxWidth * 1.7f, maxHeight) val fontSize = size * 0.8f Text( text = first, color = color, fontSize = LocalDensity.current.run { fontSize.toSp() }, modifier = Modifier.fillMaxSize(), textAlign = TextAlign.Center, ) }( Funciona con vista previa ) Aquí hay otra solución que usa BoxWithConstraints para obtener el ancho disponible y compararlo con el ancho que se necesita para diseñar el texto en una línea, usando ParagraphIntrinsics :
@Composable private fun AutosizeText( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current ) { BoxWithConstraints { var shrunkFontSize = fontSize val calculateIntrinsics = @Composable { ParagraphIntrinsics( text, TextStyle( color = color, fontSize = shrunkFontSize, fontWeight = fontWeight, textAlign = textAlign, lineHeight = lineHeight, fontFamily = fontFamily, textDecoration = textDecoration, fontStyle = fontStyle, letterSpacing = letterSpacing ), density = LocalDensity.current, resourceLoader = LocalFontLoader.current ) } var intrinsics = calculateIntrinsics() with(LocalDensity.current) { while (intrinsics.maxIntrinsicWidth > maxWidth.toPx()) { shrunkFontSize *= 0.9 intrinsics = calculateIntrinsics() } } Text( text = text, modifier = modifier, color = color, fontSize = shrunkFontSize, fontStyle = fontStyle, fontWeight = fontWeight, fontFamily = fontFamily, letterSpacing = letterSpacing, textDecoration = textDecoration, textAlign = textAlign, lineHeight = lineHeight, onTextLayout = onTextLayout, style = style ) } }Construí sobre la respuesta de Brian para admitir otras propiedades de Text que también se elevan y pueden ser utilizadas por la persona que llama.
@Composable fun AutoResizeText( text: String, fontSizeRange: FontSizeRange, modifier: Modifier = Modifier, color: Color = Color.Unspecified, 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, style: TextStyle = LocalTextStyle.current, ) { var fontSizeValue by remember { mutableStateOf(fontSizeRange.max.value) } var readyToDraw by remember { mutableStateOf(false) } Text( text = text, color = color, maxLines = maxLines, fontStyle = fontStyle, fontWeight = fontWeight, fontFamily = fontFamily, letterSpacing = letterSpacing, textDecoration = textDecoration, textAlign = textAlign, lineHeight = lineHeight, overflow = overflow, softWrap = softWrap, style = style, fontSize = fontSizeValue.sp, onTextLayout = { Timber.d("onTextLayout") if (it.didOverflowHeight && !readyToDraw) { Timber.d("Did Overflow height, calculate next font size value") val nextFontSizeValue = fontSizeValue - fontSizeRange.step.value if (nextFontSizeValue <= fontSizeRange.min.value) { // Reached minimum, set minimum font size and it's readToDraw fontSizeValue = fontSizeRange.min.value readyToDraw = true } else { // Text doesn't fit yet and haven't reached minimum text range, keep decreasing fontSizeValue = nextFontSizeValue } } else { // Text fits before reaching the minimum, it's readyToDraw readyToDraw = true } }, modifier = modifier.drawWithContent { if (readyToDraw) drawContent() } ) } data class FontSizeRange( val min: TextUnit, val max: TextUnit, val step: TextUnit = DEFAULT_TEXT_STEP, ) { init { require(min < max) { "min should be less than max, $this" } require(step.value > 0) { "step should be greater than 0, $this" } } companion object { private val DEFAULT_TEXT_STEP = 1.sp } }Y el uso se vería así:
AutoResizeText( text = "Your Text", maxLines = 3, modifier = Modifier.fillMaxWidth(), fontSizeRange = FontSizeRange( min = 10.sp, max = 22.sp, ), overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.body1, )De esta manera, pude configurar diferentes líneas máximas e incluso tener puntos suspensivos tan desbordados como el texto era demasiado grande para caber en las líneas establecidas, incluso con el tamaño más pequeño que queremos.
Ajustó una pequeña solución de Thad C
Componer versión: 1.1.0-beta02
Vista previa de trabajos
No parpadea cuando cambia el texto, los cambios de texto se manejan rápidamente (aunque sería aún mejor si el cálculo del tamaño del texto se iniciara como corrutina en otro hilo)
@Composable fun AutoSizeText( text: AnnotatedString, minTextSizeSp: Float, maxTextSizeSp: Float, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, textAlign: TextAlign? = null, style: TextStyle = LocalTextStyle.current, contentAlignment: Alignment = Alignment.TopStart, ) { check(minTextSizeSp > 0) { "Min text size should above zero" } check(minTextSizeSp < maxTextSizeSp) { "Min text size should be smaller then max text size" } BoxWithConstraints(modifier, contentAlignment = contentAlignment) { val textString = text.toString() val currentStyle = style.copy( color = color, fontStyle = fontStyle ?: style.fontStyle, fontSize = maxTextSizeSp.sp, fontWeight = fontWeight ?: style.fontWeight, fontFamily = fontFamily ?: style.fontFamily, textAlign = textAlign, ) val fontChecker = createFontChecker(currentStyle, textString) val fontSize = remember(textString) { fontChecker.findMaxFittingTextSize(minTextSizeSp, maxTextSizeSp) } Text( text = text, style = currentStyle + TextStyle(fontSize = fontSize), color = color, textAlign = textAlign ) } } @Composable private fun BoxWithConstraintsScope.createFontChecker(currentStyle: TextStyle, text: String): FontChecker { val density = LocalDensity.current return FontChecker( density = density, resourceLoader = LocalFontLoader.current, maxWidthPx = with (density) { maxWidth.toPx() }, maxHeightPx = with (density) { maxHeight.toPx() }, currentStyle = currentStyle, text = text ) } private class FontChecker( private val density: Density, private val resourceLoader: Font.ResourceLoader, private val maxWidthPx: Float, private val maxHeightPx: Float, private val currentStyle: TextStyle, private val text: String ) { fun isFit(fontSizeSp: Float): Boolean { val height = Paragraph( text = text, style = currentStyle + TextStyle(fontSize = fontSizeSp.sp), width = maxWidthPx, density = density, resourceLoader = resourceLoader, ).height return height <= maxHeightPx } fun findMaxFittingTextSize( minTextSizeSp: Float, maxTextSizeSp: Float ) = if (!isFit(minTextSizeSp)) { minTextSizeSp.sp } else if (isFit(maxTextSizeSp)) { maxTextSizeSp.sp } else { var fit = minTextSizeSp var unfit = maxTextSizeSp while (unfit - fit > 1) { val current = fit + (unfit - fit) / 2 if (isFit(current)) { fit = current } else { unfit = current } } fit.sp } }