User_52345
User_52345

Reputation: 81

How to prevent Jetpack Compose ExposedDropdownMenuBox from showing menu when scrolling

I'm trying to use Jetpack Compose's ExposedDropdownMenuBox but I can't prevent it from showing the menu when scrolling.

For example, here's the code to replicate this problem:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Surface(color = MaterialTheme.colors.background) {
                    Column(
                        modifier = Modifier
                            .padding(horizontal = 8.dp)
                            .verticalScroll(rememberScrollState()),
                        verticalArrangement = Arrangement.spacedBy(8.dp)
                    ) {
                        repeat(20){
                            ExposedDropdownMenuSample()
                        }
                    }
                }
            }
        }
    }
}

ExposedDropdownMenuSample was taken from the official samples.

This is a GIF showing the problem.

How can I prevent this from happening?

This code is using compose version 1.1.0-rc01.

Upvotes: 8

Views: 1606

Answers (3)

Akhha8
Akhha8

Reputation: 490

This issue was reported in: https://issuetracker.google.com/issues/212091796

and fixed in androidx.compose.material:material:1.4.0-alpha02

Upvotes: 2

Bruno
Bruno

Reputation: 4517

edit: now it doesn't swallow fling-motion as reported by m.reiter 😁

I was able to fix this with this ugly hack:

private fun Modifier.cancelOnExpandedChangeIfScrolling(cancelNext: () -> Unit) = pointerInput(Unit) {
    forEachGesture {
        coroutineScope {
            awaitPointerEventScope {
                var event: PointerEvent
                var startPosition = Offset.Unspecified
                var cancel = false

                do {
                    event = awaitPointerEvent(PointerEventPass.Initial)
                    if (event.changes.any()) {
                        if (startPosition == Offset.Unspecified) {
                            startPosition = event.changes.first().position
                        }

                        val distance =
                            startPosition.minus(event.changes.last().position).getDistance()
                        cancel = distance > 10f || cancel
                    }
                } while (!event.changes.all { it.changedToUp() })

                if (cancel) {
                    cancelNext.invoke()
                }
            }
        }
    }
}

then add it to the ExposedDropdownMenuBox like:

var cancelNextExpandedChange by remember { mutableStateOf(false) } //this is to prevent fling motion from being swallowed
ExposedDropdownMenuBox(
    expanded = expanded,
    onExpandedChange = {
        if (!cancelNextExpandedChange) expanded = !expanded
        cancelNextExpandedChange = false
    }, modifier = Modifier.cancelOnExpandedChangeIfScrolling() { cancelNextExpandedChange = true }
)

So it basically checks if there was a drag for more than 10 pixels? and if true, invokes the callback that sets cancelNextExpandedChange to true so it will skip the next onExpandedChange.

10 is just a magic number that worked well for my tests, but it seems to be too low for a high res screen device. I'm sure there's a better way to calculate this number... Maybe someone more experienced can help with this until we have a proper fix?

Upvotes: 2

Florian
Florian

Reputation: 1

I found a slightly less hacky workaround:

You get the info whether a scrolling is in progress from the scroll-state of the Column.

val scrollState = rememberScrollState()
val isScrolling = scrollState.isScrollInProgress

Column(
  modifier = Modifier
    .padding(horizontal = 8.dp)
    .verticalScroll(scrollState),
    ...
) ...

In the ExposedDropdownMenuBox you can then change the listener to

onExpandedChange = {
  expanded = !expanded && !isScrolling
},

=> The dropdown is never opened while scrolling. It is also automatically closed as soon as you start scrolling in the main-column. However scrolling inside the dropdown is possible. Of course you can also cahnge it to something like

expanded = if (isScrolling) expanded else !expanded

To just leave everything like it is while scrolling

Upvotes: 0

Related Questions