How can I make a TextField with a minimum height o...
# compose
c
How can I make a TextField with a minimum height of 3 lines? In Android XML world, it is achieved through
android:minLines
n
I did with
SubcomposeLayout
, but I’m not proud of the solution 😅
s
@nglauber can you share your solution?
s
very useful, thanks @nglauber!
ooh your code snippet has given me a very clever idea: I can also pad my text with
"\n".repeat(minLines)
! It works for my usecase because I also have
maxLines == minLines
.
d
sometimes intermediate characters are dropped
I've noticed that if I remove SubcomposeLayout, these issues do not exist
a
d
thank you for the assist
I think this is the relevant issue to add native support https://issuetracker.google.com/issues/122476634
Solution that I came up with
Copy code
// 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()