Skip to content

Instantly share code, notes, and snippets.

@jossiwolf
Created April 4, 2021 13:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jossiwolf/0f06894d2c07748041769c64510cd4d5 to your computer and use it in GitHub Desktop.
Save jossiwolf/0f06894d2c07748041769c64510cd4d5 to your computer and use it in GitHub Desktop.
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 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment