Last active
April 1, 2023 10:36
-
-
Save tpoisson/15835d1df5a9f10f0baea838cea1dc1e to your computer and use it in GitHub Desktop.
Android Jetpack Compose Dropdown
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Parameterized Dropdown for Jetpack Compose and Material3 | |
* @param options list of options to display | |
* @param selectedValue the selected value for this field | |
* @param itemValue method called to determine the selected value matching options | |
* @param textValue the method called on object to display its label | |
* @param placeholder the placeholder displayed in the textfield | |
* @param enabled enable or disable the field | |
* @param onChange called when option is selected | |
*/ | |
@OptIn(ExperimentalMaterial3Api::class) | |
@Composable | |
fun <T> Dropdown( | |
options: List<T>, | |
modifier: Modifier = Modifier, | |
selectedValue: T?, | |
itemValue: (T?) -> Any? = { it }, | |
textValue: (T) -> String = { it.toString() }, | |
placeholder: @Composable (() -> Unit)? = null, | |
enabled: Boolean = true, | |
loading: Boolean = false, | |
onChange: (T) -> Unit | |
) { | |
var expanded by remember { mutableStateOf(false) } | |
// We want to react on tap/press on TextField to show menu | |
ExposedDropdownMenuBox( | |
modifier = modifier, | |
expanded = expanded, | |
onExpandedChange = { | |
expanded = !expanded | |
}, | |
) { | |
Column { | |
TextField( | |
// The `menuAnchor` modifier must be passed to the text field for correctness. | |
modifier = Modifier.menuAnchor(), | |
readOnly = true, | |
enabled = enabled, | |
value = selectedValue?.let { textValue(it) } ?: "", | |
onValueChange = {}, | |
label = placeholder, | |
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, | |
colors = ExposedDropdownMenuDefaults.textFieldColors(), | |
) | |
if (loading) { | |
LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) | |
} | |
} | |
ExposedDropdownMenu( | |
expanded = expanded, | |
onDismissRequest = { expanded = false }, | |
) { | |
options.forEach { selectionOption -> | |
val currentIsSelectedValue = itemValue(selectionOption) == itemValue(selectedValue) | |
val backgroundColor = if (currentIsSelectedValue) { | |
Modifier.background(MaterialTheme.colorScheme.secondary) | |
} else { | |
Modifier | |
} | |
val menuItemColors = if (currentIsSelectedValue) { | |
MenuDefaults.itemColors(textColor = MaterialTheme.colorScheme.onSecondary) | |
} else { | |
MenuDefaults.itemColors() | |
} | |
DropdownMenuItem( | |
modifier = backgroundColor, | |
text = { Text(textValue(selectionOption)) }, | |
colors = menuItemColors, | |
onClick = { | |
onChange(selectionOption) | |
expanded = false | |
}, | |
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, | |
) | |
} | |
} | |
} | |
} | |
@Composable | |
@Preview(showSystemUi = true) | |
private fun DropdownPreviewSelectedValue() { | |
Column( | |
modifier = Modifier.padding(16.dp), | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.spacedBy(8.dp) | |
) { | |
Dropdown( | |
options = listOf("A", "B"), | |
selectedValue = "A", | |
placeholder = { Text("Pick a value") } | |
) { | |
} | |
Dropdown( | |
options = listOf("A", "B"), | |
selectedValue = "A", | |
) { | |
} | |
Dropdown( | |
enabled = false, | |
options = listOf("A", "B"), | |
selectedValue = null, | |
placeholder = { Text("This dropdown is disabled") } | |
) { | |
} | |
Dropdown( | |
modifier = Modifier.width(200.dp), | |
enabled = true, | |
options = listOf("A", "B"), | |
selectedValue = null, | |
placeholder = { Text("This dropdown has a width of 200.dp") } | |
) { | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment