4

How can I add a suffix to TextField input that flows (moves) with the user input text?

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
Mahozad
  • 18,032
  • 13
  • 118
  • 133

3 Answers3

10

With M3 starting from the 1.1.0-alpha06 you can use the suffix attribute:

    TextField(
        value = text,
        onValueChange = { text = it },
        suffix = { Text ("€") }
    )

enter image description here

Before M3 1.1.0-alpha06 or with M2 or you can use the visualTransformation attribute.

Something like:

TextField(
    value = text,
    onValueChange = { text = it },
    singleLine = true,
    visualTransformation = SuffixTransformation(" €"),
)

class SuffixTransformation(val suffix: String) : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {

        val result = text + AnnotatedString(suffix)

        val textWithSuffixMapping = object : OffsetMapping {
            override fun originalToTransformed(offset: Int): Int {
                return offset
            }

            override fun transformedToOriginal(offset: Int): Int {
                if (text.isEmpty()) return 0
                if (offset >=  text.length) return text.length                    return offset
            }
        }

        return TransformedText(result, textWithSuffixMapping )
    }
}

enter image description here

If you have the placeholder you can put a condition in the visualTransformation attribute.

Something like:

TextField(
    value = text,
    onValueChange = { text = it },
    singleLine = true,
    visualTransformation = if (text.isEmpty())
        VisualTransformation.None
    else
        SuffixTransformation(" €"),
    placeholder = { Text("Placeholder") }
)
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • Thanks for the answer. But this was a self-answered question. I think you didn't notice that :) What do you think of my answer? Actually, I derived it from [one of your posts](https://stackoverflow.com/a/67735317/8583692)! – Mahozad Nov 23 '22 at 12:17
  • Thank you for your answer, very useful (just check the suggestion of Martynas B, it's right.) If it can be useful to someone, I use this code to avoid overlapping when using BasicTextField with placeholder : ` var suffixLocal: String = "" // avoid to overlap placeholder if (!text.isEmpty()) suffixLocal = suffix val result:AnnotatedString = text + AnnotatedString(suffixLocal) val suffixOffset = suffixLocal.length ` – android_dev71 Jan 27 '23 at 16:59
  • 1
    @android_dev71 thanks for the feedback. I've update the answer with the placeholder scenario. I would prefer to put a condition in the `visualTransformation` attribute just to have more flexibility. For example you can also handle the focus state with a placeholder. – Gabriele Mariotti Jan 27 '23 at 17:14
  • what is M3 and im not able to see any suffix property i can see only trailingIcon which accepts a compose object – Praneeth Aug 31 '23 at 13:35
  • 1
    @Praneeth https://developer.android.com/jetpack/androidx/releases/compose-material3 – Gabriele Mariotti Aug 31 '23 at 13:37
  • @GabrieleMariotti how can I do this? when I try I'm able to see only trailingIcon and its adding some padding and looks weird for my suffix icon – Praneeth Aug 31 '23 at 13:40
2

I found Gabriele Mariotti's answer buggy. Needed to change transformedToOriginal function to this:

override fun transformedToOriginal(offset: Int): Int {
    if (offset > text.length) return text.length
    return offset
}
Martynas B
  • 2,843
  • 2
  • 12
  • 15
  • 1
    it's right, without this correction it gives errors like this, in my case happens with 2 fields, moving cursor by clicking from one to another o something else: `java.lang.IllegalStateException: OffsetMapping.transformedToOriginal returned invalid mapping: 5 -> 5 is not in range of original text [0, 4]` – android_dev71 Jan 27 '23 at 16:55
0

This is easily done in Compose like this:

const val SUFFIX = " $"
@Composable
fun SuffixedText() {
    var text by remember { mutableStateOf("") }
    TextField(
        text,
        singleLine = true,
        visualTransformation = SuffixTransformer(SUFFIX),
        onValueChange = { text = it }
    )
}
class SuffixTransformer(val suffix: String) : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        val result = text + AnnotatedString(suffix)
        return TransformedText(result, OffsetMapping.Identity)
    }
}

The above component probably can be used in traditional Views too. See this post

Also, see the following:

Mahozad
  • 18,032
  • 13
  • 118
  • 133
  • It has some issues. For example if you click in the suffix (inside o just after the last character) the code crashed because of OffsetMapping.Identity – Gabriele Mariotti Nov 23 '22 at 14:51
  • I tied it in a Desktop app using [Compose Multiplatform](https://github.com/JetBrains/compose-jb) v1.2.1 which uses Compose v??? on Windows and did not encounter any error. Maybe it's a regression in newer versions of Compose? – Mahozad Nov 23 '22 at 15:20