oldu
oldu

Reputation: 203

Jetpack Compose - TextField ellipsis

I am trying to create a single line text input in Jetpack Compose.

Since this input should have a fix width of 200.dp and is only one line, longer text is going to be cut off. Sometimes it looks like the cut off text does not even exist because the cut is made between two letters. In order to show the user that there is "more" text already typed I would prefer an ellipsis (e.g. This is a sample inpu...) effect.

I tried to use the default TextField and BasicTextField composables but there seems to be no easy solution.

Is there a way to create this ellipsis effect in Jetpack Compose?

Upvotes: 4

Views: 3915

Answers (2)

Inliner
Inliner

Reputation: 1659

Although the solution provided by Brian Ngure is not working, because wrong offsets trigger crashes. I used his idea to create a working one (at least for me :D).

VisualTransformation { text ->
TransformedText(buildAnnotatedString {
    if (maxSymbols < 3) {
        append(text)
    } else {
        append(text.take(maxSymbols - 3))
        append("...")
    }

}, object : OffsetMapping {
    override fun originalToTransformed(offset: Int): Int {
        return if (maxSymbols < 3) text.length else maxSymbols
    }

    override fun transformedToOriginal(offset: Int): Int {
        return text.length
    }
})

Where 3 == "...".length

In order for that to work you have to get maxSymbols that can be drawn on the screen. You can do that in the onTextLayout callback of the TextField There are ready to use variables didOverflowWidth, didOverflowHeight, hasVisualOverflow

        onTextLayout = { result ->
        isTextOverflow = result.didOverflowWidth
    },

If that's not working, you can get information about current text field width result.size.width and by comparing that with the screen width from LocalContext.current.resources.displayMetrics.widthPixels you can understand when there is an overflow. For some reason the dedicated variables are not working for me.

When the overflow happens, just use another variable to remember maxSymbols

and apply this transformation to the TextField and a decoration box, if you use one.

var isTextOverflow by remember { mutableStateOf(false) }
var maxSymbols: Int = remember(isTextOverflow) { if (isTextOverflow) text.text.length else 0 }

And also you might need to recreate the transformation as different symbols have different width and for different texts width might be different, but recreating transformation too often can be bad for performance.

Update: You might also find useful a TextMeasurer object that allows you to compare textField width with the text width if your textfield is not screen wide

    val textMeasurer = rememberTextMeasurer()
    val measuredTextWidth = textMeasurer.measure(
    text = text,
    style = textStyle
)

Upvotes: 1

Brian Ngure
Brian Ngure

Reputation: 11

I realise this is old, but I faced the same predicament. This is my solution.

@Composable
fun ellipsisVisualTransformation() = VisualTransformation { text ->
    val ellipsis = "..."
    val maxLength = 20 // Set your desired max length

    if (text.length <= maxLength) {
        TransformedText(text = text, offsetMapping = OffsetMapping.Identity)
    } else {
        TransformedText(
            AnnotatedString.Builder().apply {
                append(text.take(maxLength - ellipsis.length))
                append(ellipsis)
            }.toAnnotatedString(),
            OffsetMapping.Identity
        )
    }
}

And in my OutlinedTextField, I added this:

visualTransformation = ellipsisVisualTransformation()

Upvotes: 0

Related Questions