Reputation: 81
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
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
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
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