Reputation: 586
for some reason Compose TextField's click listener does not work for me.
@Composable
private fun ExposedDropdown(
modifier: Modifier,
list: List<String>,
priority: Int
) {
var expanded by remember { mutableStateOf(false) }
Column(modifier) {
OutlinedTextField(
value = list[priority],
onValueChange = { },
readOnly = true,
singleLine = true,
label = { Text(stringResource(id = R.string.status)) },
modifier = Modifier
.fillMaxWidth()
.clickable { Timber.i("Not working :(") }
.onFocusChanged { if (it.isFocused) expanded = !expanded },
trailingIcon = {
Icon(
imageVector = Icons.Outlined.ArrowDropDown,
contentDescription = null,
modifier = Modifier
.clickable { expanded = !expanded }
.padding(16.dp)
)
}
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
list.forEach { label ->
DropdownMenuItem(onClick = {
viewModel.setPriority(list.indexOf(label))
expanded = false
}) {
Text(text = label)
}
}
}
}
}
As you can see I come up with bad solution using onFocusChanged
but it does not work well.
For those who need context, I'm trying to do ExposedDropdown but I want it to open when I click anywhere on TextField
Upvotes: 44
Views: 35487
Reputation: 190
TextField(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick),
enabled = false,
value = currentSelection,
onValueChange = { },
label = {
}
)
use the enabled = false and clickable modifier.
Upvotes: 2
Reputation: 351
The suggestion of setting the enabled attribute as false works, but this affects the style. If you want it to visually match an enabled TextField then set the colors attribute accordingly:
OutlinedTextField(
value = "Example",
enabled = false,
onValueChange = {},
modifier = Modifier.clickable { doSomeBehavior() },
colors = OutlinedTextFieldDefaults.colors().copy(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledIndicatorColor = MaterialTheme.colorScheme.outline,
disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
//For Icons
disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant)
)
Upvotes: 23
Reputation: 3077
with compose 1.0.2 it works by default. Need to add line enabled = false
Example
@Composable
fun SelectableTextField(
modifier: Modifier = Modifier,
textValue: String,
onClick: () -> Unit
) {
TextField(
value = textValue,
onValueChange = {},
modifier = modifier
.fillMaxWidth()
.clickable { onClick() },
enabled = false
)
}
to remove ripple effect, use such extension
inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier =
this.then(
composed {
clickable(indication = null,
interactionSource = remember { MutableInteractionSource() }) {
onClick()
}
})
Upvotes: 30
Reputation: 519
val interactionSource = remember { MutableInteractionSource() }
val isPressed: Boolean by interactionSource.collectIsPressedAsState()
LaunchedEffect(isPressed){
if (isPressed) {
// Click action
}
}
TextField(
value = textFieldValue,
onValueChange = onTextFieldChange,
interactionSource = interactionSource
)
Upvotes: 10
Reputation: 364730
The clickable
modifier currently (1.0.0-beta08
) doesn't work with a TextField
.
It is a workaround, not a real solution.
Since your TextField
is readonly, you can wrap the OutlinedTextField
with in a Box
using a second Box
to handle the click.
val focusRequester = FocusRequester.createRefs()
val interactionSource = remember { MutableInteractionSource() }
Box() {
OutlinedTextField(
//...your code
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester)
}
if (!expanded) {
Box(modifier = Modifier
.matchParentSize()
.clickable(
onClick = {
expanded = !expanded
focusRequester.requestFocus() //to give the focus to the TextField
},
interactionSource = interactionSource,
indication = null //to avoid the ripple on the Box
))
}
}
Upvotes: 9
Reputation: 5734
Another possible workaround can be this:
import kotlinx.coroutines.flow.collect
TextField(
value = ...,
onValueChange = { ... },
interactionSource = remember { MutableInteractionSource() }
.also { interactionSource ->
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect {
if (it is PressInteraction.Release) {
// works like onClick
}
}
}
}
)
Upvotes: 53
Reputation: 754
Here is a possible solution. I've created a collectClickAsState()
composable based on androidx.compose.foundation.interaction.collectIsPressedAsState
:
@Composable
fun CustomTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
onClick: (() -> Unit)? = null,
) {
val onClickSource = remember { MutableInteractionSource() }
if (onClick != null) {
if (onClickSource.collectClickAsState().value) {
onClick.invoke()
}
}
TextField(
value = value,
onValueChange = onValueChange,
interactionSource = onClickSource,
enabled = enabled,
readOnly = onClick != null,
modifier = modifier
// add clickable to work with talkback/accessibility
.clickable(enabled = enabled) { onClick?.invoke() },
)
}
@Composable
fun InteractionSource.collectClickAsState(): State<Boolean> {
val onClick = remember { mutableStateOf(false) }
LaunchedEffect(this) {
var wasPressed = false
interactions.collect { interaction ->
when (interaction) {
is PressInteraction.Press -> {
wasPressed = true
}
is PressInteraction.Release -> {
if (wasPressed) onClick.value = true
wasPressed = false
}
is PressInteraction.Cancel -> {
wasPressed = false
}
}
// reset state with some delay otherwise it can re-emit the previous state
delay(10L)
onClick.value = false
}
}
return onClick
}
With this solution, the text field is still focusable, the text is selectable and it will use the correct UI enabled state.
Upvotes: 2