mrzbn
mrzbn

Reputation: 665

Equivalent of "expandedHintEnabled" in Jetpack Compose TextField

Is there any way to put OutlinedTextField's label on top (expanded) and still have the placeholder, when OutlinedTextField is not in focus?

In xml layouts we have TextInputLayout that has expandedHintEnabled attribute that does the expanding.

enter image description here

Upvotes: 2

Views: 1950

Answers (2)

sardonicus87
sardonicus87

Reputation: 105

I just wrestled with this and came up with a fairly easy and elegant solution, assuming you're controlling the value of the OutlinedTextField from a viewmodel...

var isFocused by rememberSaveable { mutableStateOf(false) }

OutlinedTextField(
    value = if (viewmodel.stringvalue.isEmpty() && !isFocused) { 
        "your placeholder text" } else { 
        viewmodel.stringvalue },
    label = { "your label" },
    modifier = Modifier
        .onFocusChanged { isFocused = it.isFocused },
    placeholder = { Text(text ="your placeholder text") },
    // any other parameters

With this, if you don't want to use a placeholder, simply change "your placeholder text" to a space (" "), (you must have at least a space).

The OutlinedTextField will place the label in the upper position whenever it is focused or whenever the value parameter is not an empty string. Even if you set a placeholder, the label will force it's way in.

So what we do is monitor the focused state and if the OutlinedTextField is not in focus and the value is blank, we replace the value with the placeholder. This creates a value in the field, forcing the label into the upper position.

When a user selects the field, it's focus state changes and the label moves up and the field is now actually blank (the non-blank value is set only if the field is not focused and is empty), but because the field is blank, the placeholder you place in the "value" parameter must also be placed in the "placeholder" parameter (if you're using a placeholder, if you don't want a place holder and are passing a space, you don't need to define the placeholder parameter).

And if for instance you're making the placeholder text have some other parameters like a particular style or fontweight or something, you can apply that to the fake "placeholder" in the value parameter by setting the OutlineTextField's "textStyle" parameter in the same way as the value:

textStyle = if (viewmodel.stringvalue.isEmpty() && !isFocused) { 
    LocalTextStyle.current.copy(
        // placeholder text style parameters
    ) } else { LocalTextStyle.current }

And if you've got more than one field you're doing this with, each one will need a separate var = isFocused (isFocused1, isFocused2, etc).

Upvotes: 0

Gabriele Mariotti
Gabriele Mariotti

Reputation: 364730

Currently the placeholder applies an alpha modifier with this condition InputPhase.UnfocusedEmpty -> if (showLabel) 0f else 1f and there isn't a parameter to achieve the same behaviour of expandedHintEnabled.

A workaround can be to use a visualTransformation to display a placeholder when the text is empty, removing the placeholder parameter.

Something like:

    val textColor = if (text.isEmpty())
        MaterialTheme.colors.onSurface.copy(ContentAlpha.medium)
    else
        LocalContentColor.current.copy(LocalContentAlpha.current)

    val textStyle = if (text.isEmpty())
        LocalTextStyle.current.merge(MaterialTheme.typography.subtitle1)
    else
        LocalTextStyle.current

    TextField(
        value = text,
        onValueChange = { text = it },
        //placeholder = { Text("Placeholder") },
        label = { Text("Label") },
        visualTransformation = if (text.isEmpty())
            PlaceholderTransformation("Placeholder")
        else VisualTransformation.None,
        textStyle = textStyle,
        colors = TextFieldDefaults.textFieldColors(
            textColor = textColor
        )
    )

enter image description here

with:

class PlaceholderTransformation(val placeholder: String) : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        return PlaceholderFilter(text, placeholder)
    }
}

fun PlaceholderFilter(text: AnnotatedString, placeholder: String): TransformedText {

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

        override fun transformedToOriginal(offset: Int): Int {
            return 0
        }
    }

    return TransformedText(AnnotatedString(placeholder), numberOffsetTranslator)
}

Upvotes: 7

Related Questions