How to change TextField's highlighted text color on Jetpack Compose?
G

2

30

In currently transitioning my app to Jetpack compose and I'm facing some problems to adapt my current color palette in some cases.

I have some TextInputLayout on my xml files that inherit the highlight text color from SECUNDARY color on my theme.

<style name="Theme.MyApp" parent="Theme.MaterialComponents.Light.NoActionBar">
    ...
    <item name="colorPrimary">@color/blue</item>
    <item name="colorPrimaryVariant">@color/blue</item>
    <item name="colorSecondary">@color/red</item>
    <item name="colorSecondaryVariant">@color/red</item>
    ...
</style>

TextInputLayout with theme's secondary colour - xml

The problem is that my TextField on compose inherit the highlight text color from the PRIMARY color on MaterialTheme.

MaterialTheme(
    colors = Colors(
        primary = Color.Blue,
        ...
        secondary = Color.Red,
        ...
    ),
    content = content,
    typography = MaterialTheme.typography,
    shapes = MaterialTheme.shapes,
) {
   TextField(...)
}

TextField with theme's primary color - compose

I overrode the colors parameter on TextField but none seems to affect this colour.

Would there be a way of overriding the highlight color on compose without changing the colors on MaterialTheme? I would like to avoid that since it could cause problems on other screens that would use same theme.

Gumbotil answered 13/8, 2021 at 23:40 Comment(1)
Commenting only so googling is easier: textSelectHandle for compose, text selection colorLopez
H
75

The colors used for text selection by text and text field components are provided by LocalTextSelectionColors.current.

You can provide a custom TextSelectionColors using:

val customTextSelectionColors = TextSelectionColors(
    handleColor = Red,
    backgroundColor = Red.copy(alpha = 0.4f)
)

CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) {
    TextField(
        value = text,
        onValueChange = { text = it },
        colors = TextFieldDefaults.textFieldColors(cursorColor = Red)
    )
}

If you want to change also the cursor color add colors = TextFieldDefaults.textFieldColors(cursorColor = Red).

enter image description here

Horary answered 14/8, 2021 at 6:20 Comment(4)
What about for a BasicTextField?Counteraccusation
@CharlesWoodson use the same LocalTextSelectionColorsHorary
It will work for BasicTextField alsoLheureux
Can I define it at the Theme level? Wrapping it in TextField makes it become writing more duplication code.Intrauterine
R
0

Both: handle and selection background can be altered with a help of composition local provider.

An example color definition (prefer colorTheme rather than using a color definition directly):

val Ecstasy = Color(0xFFF58220)

Selection colors definition:

val selectionColors = TextSelectionColors(
  backgroundColor = Ecstasy.copy(alpha = 0.37f),
  handleColor = Ecstasy
)

It works with BasicTextField (a BasicTextField2 renamed in Compose 1.7.0-beta) but I see no reasons why it shouldn't work with TextField or any MaterialTextField-s.

CompositionLocalProvider(
  LocalTextSelectionColors provides selectionColors
) {
  BasicTextField(...)
}

As using composition provider for every single use of the text field can be annoying, you can always consider implementing your own composable.

This is exactly what the documentation advices:

Write Jetpack Compose applications with ready to use building blocks and extend foundation to build your own design system pieces.

If composable does not work as expected, it is always possible to create one that fits the requirements.

  • There is a foundation layer with text field basics.
  • There is material layer with Material specification implementation.
  • There might be your design system level implementation that uses local text selection composition provider under the hood.

Then it becomes possible to use the design system level composable instead of calling BasicTextField directly.

Recently I've created a text field that follows the requirements:

  • starts from the filled text field type (the second option is outlined)
  • redefines the shape to not use any corner radius
  • resets the indication color for error
  • draws a red border for error state instead
  • in the future it will also replace the original error text with a custom design wrapped in its own background and an anchor at the top

The results are:

enter image description here enter image description here

An implementation is as simple as the following one:

fun TextField(
  modifier: Modifier = Modifier,
  state: TextFieldUiState,
  onValueChange: Consumer<String>,
  label: String? = null,
  keyboardActions: KeyboardActions = KeyboardActions(),
  keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
  maxLines: Int = Int.MAX_VALUE,
  singleLine: Boolean = false,
  visualTransformation: VisualTransformation = VisualTransformation.None,
  colors: TextFieldColors = TextFieldDefaults.colors()
) {
  fun TextField(
    modifier: Modifier = Modifier,
    state: TextFieldUiState,
    onValueChange: Consumer<String>,
    label: String? = null,
    keyboardActions: KeyboardActions = KeyboardActions(),
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
    maxLines: Int = Int.MAX_VALUE,
    singleLine: Boolean = false,
    visualTransformation: VisualTransformation = VisualTransformation.None,
    colors: TextFieldColors = TextFieldDefaults.colors()
  ) {

    val textStyle =
      TextStyle(color = colors.focusedTextColor)

    val borderWidth = when (state.isError()) {
      true -> 1.dp
      false -> 0.dp
    }

    val borderColor = when (state.isError()) {
      true -> colors.errorIndicatorColor
      false -> Transparent
    }

    CompositionLocalProvider(
      LocalTextSelectionColors provides colors.textSelectionColors
    ) {
      BasicTextField(
        value = state.value,
        onValueChange = onValueChange,
        modifier = modifier.border(
          width = borderWidth,
          color = borderColor
        ),
        enabled = state.enabled,
        keyboardActions = keyboardActions,
        keyboardOptions = keyboardOptions,
        singleLine = singleLine,
        maxLines = maxLines,
        textStyle = textStyle,
        cursorBrush = SolidColor(colors.cursorColor),
        visualTransformation = visualTransformation,
        decorationBox = @Composable { innerTextField ->
          DecorationBox(
            value = state.value,
            visualTransformation = visualTransformation,
            innerTextField = innerTextField,
            label = label?.let { { Text(text = it) } },
            shape = RectangleShape,
            singleLine = singleLine,
            enabled = state.enabled,
            interactionSource = remember { MutableInteractionSource() },
            colors = colors
          )
        }
      )
    }
  }
}

Most of the theming is done through the custom implementation of TextFieldDefaults.colors() that defines TextSelectionColors as one of its properties.

Renewal answered 1/6 at 15:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.