Detect click in Compose TextField

Viewed 3053

I am trying to implement an onClick handler for a Compose TextField. Current functionality is to handle the TextField click but disable manually editing of the field. For my use case I would like to handle the click and do something else. I would like to keep the look and feel of a TextField, and would like the focus animation to also happen.

the readOnly property gives me what I want from a UX perspective, however when I click on the TextField the onClick handler is not called.

TextField(
  value = text,
  onValueChange = { text = it},
  readOnly = true,
  modifier = Modifier
    .clickable(onClick = {
      Log.i("TextField", "Clicked")
    })
)

I have also tried to use pointerInput, how I am having the same problem.

TextField(
  value = text,
  onValueChange = { text = it},
  readOnly = true,
  modifier = Modifier
    .pointerInput(Unit) {
      detectTapGestures(onTap = {
        Log.i("TextField", "Clicked")
      }
    }
)

With Compose being so new its hard to tell if this is a bug or intended.

3 Answers

Maybe the better way to detect click events in text field is using interaction source.

step 1 : - Create an interaction source

 val source = remember {
    MutableInteractionSource()
}

step 2: - pass it to text field

 OutlinedTextField(
    value = text,
    readOnly = true,
    onValueChange = {},
    textStyle = MaterialTheme.typography.body1.copy(
        lineHeight = if (isFocused) 25.sp else TextUnit.Unspecified,
        fontWeight = if (isFocused) FontWeight.SemiBold else FontWeight.Normal
    ),
    label = {
        Text(
            text = label,
            fontWeight = if (isFocused) FontWeight.SemiBold else FontWeight.Normal,
            style = MaterialTheme.typography.caption
        )
    },
    interactionSource = source,
    colors = TextFieldDefaults.outlinedTextFieldColors(
        textColor = if (isFocused) MaterialTheme.colors.primary else LocalContentColor.current
    ),
    maxLines = if (isFocused) Int.MAX_VALUE else 2,
    modifier = Modifier
        .padding(
            start = COUNTER_WIDTH + 16.dp,
            top = padding,
            bottom = padding,
            end = 16.dp
        )
        .fillMaxWidth()
        .verticalScroll(enabled = isFocused, state = rememberScrollState())
        .animateContentSize(animationSpec = tween(DURATION))
)

step: 3- collect pressed flow as state and observe changes to it.

 if ( source.collectIsPressedAsState().value)
        onClicked()

Using readOnly = true the TextField is still enabled and the first click gives the focus to the field. You have to perform a double click to handle your onClick function.

Otherwise you can use the enabled property:

   TextField(
        enabled = false,
        modifier = Modifier.clickable(onClick = {/* ... */})
    )

Something like:

TextField(
    value = text,
    onValueChange = { text = it},
    enabled = false,
    modifier = Modifier
        .clickable { text= "Clicked"},
    colors = TextFieldDefaults.textFieldColors(
        disabledTextColor = LocalContentColor.current.copy(LocalContentAlpha.current),
        disabledLabelColor =  MaterialTheme.colors.onSurface.copy(ContentAlpha.medium)
    )
)

enter image description here

To me, it seems the top answer has one major problem.

if ( source.collectIsPressedAsState().value)
    onClicked()

onClicked() will trigger even if press is canceled because you are not waiting to see the end state after the press. When you are scrolling, if your touch is in the same position as the textField, onClick will get triggered and I'm sure that's unwanted interaction 99% of the time.

val pressedState=source.interactions.collectAsState(
      initial = PressInteraction.Cancel(PressInteraction.Press(Offset.Zero)))
       
    if (pressedState.value is PressInteraction.Release)
    {
        dropDownExpanded.value = true
        onClick?.invoke()
        source.tryEmit(PressInteraction.Cancel(PressInteraction.Press(Offset.Zero)))
    }

if you do something like this it eliminates the unwanted onClick while scrolling

Related