android 在Jetpack合成中指定文本的最小行数

blmhpbnm  于 2023-01-15  发布在  Android
关注(0)|答案(6)|浏览(131)

出于各种原因,Text的高度应始终至少等于x行文本,无论其文本行是否少于x行。TextBasicText合成对象只有maxLines参数,而没有minLines参数
我尝试了以下方法(x = 3):

Text(
    modifier = Modifier.sizeIn(minHeight = with(LocalDensity.current) {
       (42*3).sp.toDp()
    }),
    color = MaterialTheme.colors.onPrimary,
    text = "Sample", textAlign = TextAlign.Center,
    style = MaterialTheme.typography.h2 /* fontSize = 42 */,
    lineHeight = 42.sp
)

结果高度小于文本包含3行时的高度
回到View World Android中,我们可以简单地使用minLines=3,如何在Jetpack Compose中实现这一点?

myss37ts

myss37ts1#

您的代码基本正确,只需将lineHeight设置为fontSize*4/3

var lineHeight = MaterialTheme.typography.h2.fontSize*4/3

Text(
    modifier = Modifier.sizeIn(minHeight = with(LocalDensity.current) {
       (lineHeight*3).toDp()
    }),
    color = MaterialTheme.colors.onPrimary,
    text = "Sample", textAlign = TextAlign.Center,
    style = MaterialTheme.typography.h2,
    lineHeight = lineHeight
)

但是,您可以使用onTextLayout回调执行类似的操作,而无需计算:

fun main() = Window {
    var text by remember { mutableStateOf("Hello, World!") }
    var lines by remember { mutableStateOf(0) }

    MaterialTheme {
        Button(onClick = {
            text += "\nnew line"
        }) {
            Column {
                Text(text,
                    maxLines = 5,
                    style = MaterialTheme.typography.h2,
                    onTextLayout = { res -> lines = res.lineCount })
                for (i in lines..2) {
                    Text(" ", style = MaterialTheme.typography.h2)
                }
            }
        }
    }
}
uubf1zoe

uubf1zoe2#

当我们是waiting for Google implements this feature时,您可以使用此解决方法:

@Preview
@Composable
fun MinLinesPreview() {
    lateinit var textLayoutResult: TextLayoutResult

    val text = "Title\ntitle\nTITLE\nTitle"
//    val text = "title\ntitle\ntitle\ntitle"
//    val text = "title\ntitle"
//    val text = "title"

    Text(
        modifier = Modifier.fillMaxWidth(),
        text = text.addEmptyLines(3), // ensures string has at least N lines,
        textAlign = TextAlign.Center,
        maxLines = 4,
    )
}

fun String.addEmptyLines(lines: Int) = this + "\n".repeat(lines)

现在,不管字符串内容如何,Text都具有相同的高度:
第一节第一节第一节第一节第二节第一节第三节第一节
此解决方案比基于onTextLayout中的线高计算Text的底部偏移要容易得多(spoiler:开始、中心和最后一行具有不同高度)

wtzytmuj

wtzytmuj3#

如果**Text的一个额外的重新组合**对您来说是合适的,您也可以使用TextonTextLayout回调作为一种解决方案,直到Google官方支持最少行:

val minLineCount = 4
var text by remember { mutableStateOf(description) }
Text(
    text = text,
    maxLines = minLineCount, // optional, if you want the Text to always be exactly 4 lines long
    overflow = TextOverflow.Ellipsis, // optional, if you want ellipsizing
    textAlign = TextAlign.Center,
    onTextLayout = { textLayoutResult ->
        // the following causes a recomposition if there isn't enough text to occupy the minimum number of lines!
        if ((textLayoutResult.lineCount) < minLineCount) {
            // don't forget the space after the line break, otherwise empty lines won't get full height!
            text = description + "\n ".repeat(minLineCount - textLayoutResult.lineCount) 
        }
    },
    modifier = Modifier.fillMaxWidth()
)

这也将正确地工作与椭圆化和任何类型的字体填充,线高度样式等设置你的心的愿望。
“假”的4行文本(比如说,末尾有2个空行)的高度与“真实的的”4行文本(其中4行文本已被完全占用)相同。这一点通常非常重要,例如,在将多个wrap_content高度的卡片水平放置在文本旁边时(与maxLines结合使用)应该决定卡片的高度,而所有卡片应该有相同的高度(而且它应该在常规的 * 和 * 高的语言中工作,比如缅甸语)。

  • 请注意,这在Android Studio的预览版中不起作用。我猜,出于性能原因,Studio不允许在预览版中进行重组。*
zdwk9cvp

zdwk9cvp4#

下面是我提出的一个解决方案,它可以将高度设置为特定的行数(您可以修改修改器使其成为minLines)。它的灵感来自于compose SDK中的代码

// Inspiration: https://github.com/androidx/androidx/blob/6075c715aea671a616890dd7f0fc9a50d96e75b9/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/MaxLinesHeightModifier.kt#L38
fun Modifier.minLinesHeight(
minLines: Int,
textStyle: TextStyle
) = composed {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current

val resolvedStyle = remember(textStyle, layoutDirection) {
    resolveDefaults(textStyle, layoutDirection)
}
val resourceLoader = LocalFontLoader.current

val heightOfTextLines = remember(
    density,
    textStyle,
    layoutDirection
) {
    val lines = (EmptyTextReplacement + "\n").repeat(minLines - 1)

    computeSizeForDefaultText(
        style = resolvedStyle,
        density = density,
        text = lines,
        maxLines = minLines,
        resourceLoader
    ).height
}

val heightInDp: Dp = with(density) { heightOfTextLines.toDp() }
val heightToSet = heightInDp + OutlinedTextBoxDecoration

Modifier.height(heightToSet)
}

// Source: https://github.com/androidx/androidx/blob/6075c715aea671a616890dd7f0fc9a50d96e75b9/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt#L61
fun computeSizeForDefaultText(
style: TextStyle,
density: Density,
text: String = EmptyTextReplacement,
maxLines: Int = 1,
resourceLoader: Font.ResourceLoader
): IntSize {
val paragraph = Paragraph(
    paragraphIntrinsics = ParagraphIntrinsics(
        text = text,
        style = style,
        density = density,
        resourceLoader = resourceLoader
    ),

    maxLines = maxLines,
    ellipsis = false,
    width = Float.POSITIVE_INFINITY
)

return IntSize(paragraph.minIntrinsicWidth.ceilToIntPx(), paragraph.height.ceilToIntPx())
}

// Source: https://github.com/androidx/androidx/blob/6075c715aea671a616890dd7f0fc9a50d96e75b9/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt#L47
internal const val DefaultWidthCharCount = 10
internal val EmptyTextReplacement = "H".repeat(DefaultWidthCharCount)

// Needed because paragraph only calculates the height to display the text and not the entire height
// to display the decoration of the TextField Widget
internal val OutlinedTextBoxDecoration = 40.dp

// Source: https://github.com/androidx/androidx/blob/6075c715aea671a616890dd7f0fc9a50d96e75b9/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt#L296
internal fun Float.ceilToIntPx(): Int = ceil(this).roundToInt()

有关此实现和其他选项的更多讨论,请参见:
https://kotlinlang.slack.com/archives/CJLTWPH7S/p1621789835172600

41zrol4v

41zrol4v5#

从M2 1.4.0-alpha02和M3 1.1.0-alpha02开始,您可以在Text中使用**minLines**属性:

Text(
       text = "MinLines = 3",
       modifier = Modifier.fillMaxWidth().background(Yellow),
       minLines = 3
   )

注意,minLines是根据最小可见线数的最小高度。要求1 <= minLines <= maxLines
您可以将其与M2和M3一起使用。

oo7oh9g9

oo7oh9g96#

创建自定义文本

它在@Preview中不起作用,但在运行时

@Composable
fun MinLineText(
    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,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    minLines: Int = 0,
    onTextLayout: (TextLayoutResult) -> Unit = {},
    style: TextStyle = LocalTextStyle.current
) {
    var mText by remember { mutableStateOf(text) }

    Text(
        mText,
        modifier,
        color,
        fontSize,
        fontStyle,
        fontWeight,
        fontFamily,
        letterSpacing,
        textDecoration,
        textAlign,
        lineHeight,
        overflow,
        softWrap,
        maxLines,
        {
            if (it.lineCount < minLines) {
                mText = text + "\n".repeat(minLines - it.lineCount)
            }
            onTextLayout(it)
        },
        style,
    )

}

用法

MinLineText(
    text = "a sample text",
    minLines = 2,
)

相关问题