EmergeTools logo
🙏 Supported by Emerge Tools
The platform for all your mobile performance needs

Exposed Dropdown Menu

Author: Jossi Wolf

An implementation of the Material Design Exposed Dropdown Menu component.

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
/**
* A basic implementation of the Exposed Dropdown Menu component
*
* @see https://material.io/components/menus#exposed-dropdown-menu
*/
@Composable
fun ExposedDropdownMenu(
items: List<String>,
selected: String = items[0],
onItemSelected: (String) -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
val interactionSource = remember { MutableInteractionSource() }
LaunchedEffect(interactionSource) {
interactionSource.interactions
.filter { it is PressInteraction.Press }
.collect {
expanded = !expanded
}
}
ExposedDropdownMenuStack(
textField = {
OutlinedTextField(
value = selected,
onValueChange = {},
interactionSource = interactionSource,
readOnly = true,
trailingIcon = {
val rotation by animateFloatAsState(if (expanded) 180F else 0F)
Icon(
rememberVectorPainter(Icons.Default.ArrowDropDown),
contentDescription = "Dropdown Arrow",
Modifier.rotate(rotation),
)
}
)
},
dropdownMenu = { boxWidth, itemHeight ->
Box(
Modifier
.width(boxWidth)
.wrapContentSize(Alignment.TopStart)
) {
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
items.forEach { item ->
DropdownMenuItem(
modifier = Modifier
.height(itemHeight)
.width(boxWidth),
onClick = {
expanded = false
onItemSelected(item)
}
) {
Text(item)
}
}
}
}
}
)
}
@Composable
private fun ExposedDropdownMenuStack(
textField: @Composable () -> Unit,
dropdownMenu: @Composable (boxWidth: Dp, itemHeight: Dp) -> Unit
) {
SubcomposeLayout { constraints ->
val textFieldPlaceable =
subcompose(ExposedDropdownMenuSlot.TextField, textField).first().measure(constraints)
val dropdownPlaceable = subcompose(ExposedDropdownMenuSlot.Dropdown) {
dropdownMenu(textFieldPlaceable.width.toDp(), textFieldPlaceable.height.toDp())
}.first().measure(constraints)
layout(textFieldPlaceable.width, textFieldPlaceable.height) {
textFieldPlaceable.placeRelative(0, 0)
dropdownPlaceable.placeRelative(0, textFieldPlaceable.height)
}
}
}
private enum class ExposedDropdownMenuSlot { TextField, Dropdown }

Have a project you'd like to submit? Fill this form, will ya!

If you like this snippet, you might also like:

Maker OS is an all-in-one productivity system for developers

I built Maker OS to track, manage & organize my life. Now you can do it too!