4

I have an IconButton in the trailingIcon of OutlinedTextField like:

OutlinedTextField(
    modifier = Modifier.weight(1f),
    label = { Text(text = "Label") },
    value = text,
    onValueChange = { text = it },
    trailingIcon = {
        IconButton2(onClick = {
            println("onClick")
        }, onLongClick = {
            println("onLongClick shows TextToolbar")
        }) {
            Icon(
                imageVector = Icons.Filled.Menu,
                contentDescription = null
            )
        }
    }
)

IconButton2 is just a copy of IconButton but with combinedClickable to include onLongClick instead of clickable.

The problem is that when I long click IconButton2, it shows the TextToolbar for the TextField. Doesn't matter what I do, the text field will handle long click, show the TextToolbar and provide haptic feedback.

Even if I use pointerInput with awaitPointerEvent and consumeAllChanges (like here) it still triggers it. The TextField doesn't answer to any tap or anything but if I long click it, it answers!

The workaround I'm doing for now is wrapping the text field in a Row and add the IconButton beside it instead of "inside" but I needed to have the icon button as the trailingIcon.

Is there any way to properly do it?

Compose 1.0.3 and 1.1.0-alpha05 both behaves the same.

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
Bruno
  • 4,337
  • 12
  • 42
  • 55

1 Answers1

0

I ended up making a small hack that seems to be working fine, so basically I add a dummy Box as the trailingIcon to get the position of it, then I add an IconButton outside of it (both wrapped in a Box) and I also get the position of it + I offset it using the position of the dummy box. Not the ideal solution but works fine.

Here's the full source if anyone needs it:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Surface(
                    modifier = Modifier.padding(16.dp),
                    color = MaterialTheme.colors.background
                ) {
                    var text by remember { mutableStateOf("") }
                    var trailingIconOffset by remember { mutableStateOf(Offset.Zero) }
                    var iconButtonOffset by remember { mutableStateOf(Offset.Zero) }
                    val colors = TextFieldDefaults.outlinedTextFieldColors()

                    Column {
                        //With hack
                        Box {
                            OutlinedTextField(
                                modifier = Modifier.fillMaxWidth(),
                                label = { Text(text = "With hack") },
                                value = text,
                                onValueChange = { text = it },
                                trailingIcon = {
                                    Box(modifier = IconButtonSizeModifier
                                        .onGloballyPositioned {
                                            trailingIconOffset = it.positionInRoot()
                                        }
                                    )
                                },
                                colors = colors
                            )

                            val contentColor by colors.trailingIconColor(
                                enabled = true,
                                isError = false
                            )

                            CompositionLocalProvider(
                                LocalContentColor provides contentColor,
                                LocalContentAlpha provides contentColor.alpha
                            ) {
                                IconButton2(
                                    modifier = Modifier
                                        .onGloballyPositioned {
                                            iconButtonOffset = it.positionInRoot()
                                        }
                                        .absoluteOffset {
                                            IntOffset(
                                                (trailingIconOffset.x - iconButtonOffset.x).toInt(),
                                                (trailingIconOffset.y - iconButtonOffset.y).toInt()
                                            )
                                        },
                                    onClick = {
                                        text = "onClick"
                                    },
                                    onLongClick = {
                                        text = "onLongClick"
                                    }
                                ) {
                                    Icon(imageVector = Icons.Filled.Menu, contentDescription = null)
                                }
                            }
                        }

                        //Without hack
                        Box {
                            OutlinedTextField(
                                modifier = Modifier.fillMaxWidth(),
                                label = { Text(text = "Without hack") },
                                value = text,
                                onValueChange = { text = it },
                                trailingIcon = {
                                    IconButton2(
                                        onClick = {
                                            text = "onClick"
                                        },
                                        onLongClick = {
                                            text = "onLongClick"
                                        }
                                    ) {
                                        Icon(
                                            imageVector = Icons.Filled.Menu,
                                            contentDescription = null
                                        )
                                    }
                                },
                                colors = colors
                            )
                        }
                    }
                }
            }
        }
    }
}

private val RippleRadius = 24.dp
private val IconButtonSizeModifier = Modifier.size(48.dp)

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun IconButton2(
    modifier: Modifier = Modifier,
    onClick: () -> Unit,
    onLongClick: (() -> Unit)? = null,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    content: @Composable () -> Unit
) {
    Box(
        modifier = modifier
            .combinedClickable(
                onClick = onClick,
                onLongClick = onLongClick,
                enabled = enabled,
                role = Role.Button,
                interactionSource = interactionSource,
                indication = rememberRipple(bounded = false, radius = RippleRadius)
            )
            .then(IconButtonSizeModifier),
        contentAlignment = Alignment.Center
    ) {
        val contentAlpha = if (enabled) LocalContentAlpha.current else ContentAlpha.disabled
        CompositionLocalProvider(LocalContentAlpha provides contentAlpha, content = content)
    }
}
Bruno
  • 4,337
  • 12
  • 42
  • 55