Ich habe mich gefragt, ob es eine Lösung für das Dropdown-Menü „Exposed“ für Jetpack Compose gibt? Ich konnte in Jetpack Compose keine geeignete Lösung für diese Komponente finden. Irgendeine Hilfe?
Offengelegtes Dropdown-Menü für Jetpack-Komposition
Stefan
Gabriele Mariotti
Der M2 (ab der Version 1.1.0-alpha06
) Und M3 Habe die Umsetzung von ExposedDropdownMenu
bezogen auf ExposedDropdownMenuBox
mit TextField
Und DropdownMenu
innen.
Etwas wie:
val options = listOf("Option 1", "Option 2", "Option 3", "Option 4", "Option 5")
var expanded by remember { mutableStateOf(false) }
var selectedOptionText by remember { mutableStateOf(options[0]) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
readOnly = true,
value = selectedOptionText,
onValueChange = { },
label = { Text("Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
options.forEach { selectionOption ->
DropdownMenuItem(
onClick = {
selectedOptionText = selectionOption
expanded = false
}
){
Text(text = selectionOption)
}
}
}
}
Wenn Sie verwenden M3 (androidx.compose.material3
) musst du auch bestehen menuAnchor
Modifikator für die TextField
:
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded },
) {
TextField(
//...
modifier = Modifier.menuAnchor()
)
ExposedDropdownMenu(){ /*.. */ }
}
Auch im M3 im DropdownMenuItem
Sie müssen den Inhalt in verschieben text
Parameter:
DropdownMenuItem(
text = { Text(text = selectionOption) },
onClick = {
selectedOptionText = selectionOption
expanded = false
}
)
Mit der M2-Version 1.0.x
Es gibt keine eingebaute Komponente.
Sie können a verwenden OutlinedTextField
+ DropdownMenu
. Es ist wichtig, sie einzuwickeln Box
. Auf diese Weise wird das TextField als „Anker“ verwendet.
Es handelt sich lediglich um eine grundlegende (sehr einfache) Implementierung:
var expanded by remember { mutableStateOf(false) }
val suggestions = listOf("Item1","Item2","Item3")
var selectedText by remember { mutableStateOf("") }
var textfieldSize by remember { mutableStateOf(Size.Zero)}
val icon = if (expanded)
Icons.Filled.ArrowDropUp //it requires androidx.compose.material:material-icons-extended
else
Icons.Filled.ArrowDropDown
Box() {
OutlinedTextField(
value = selectedText,
onValueChange = { selectedText = it },
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
//This value is used to assign to the DropDown the same width
textfieldSize = coordinates.size.toSize()
},
label = {Text("Label")},
trailingIcon = {
Icon(icon,"contentDescription",
Modifier.clickable { expanded = !expanded })
}
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(with(LocalDensity.current){textfieldSize.width.toDp()})
) {
suggestions.forEach { label ->
DropdownMenuItem(onClick = {
selectedText = label
}) {
Text(text = label)
}
}
}
}
-
Im Issuetracker von Google wurde ein Fehler gemeldet: https://issuetracker.google.com/issues/173532272 Hoffentlich wird es implementiert, bevor Stable veröffentlicht wird.
– Chris
24. Mai 2021 um 22:07 Uhr
-
Gibt es eine Möglichkeit, die Breite von DropdownMenu an die von OutlinedTextField anzupassen?
– Ali_Waris
23. Juli 2021 um 10:07 Uhr
-
Gibt es eine Möglichkeit, das Menü über dem Textfeld statt darunter zu positionieren?
– Kofi
13. September 2021 um 14:17 Uhr
-
@Ali_Waris Ich fand diese Antwort hilfreich.
– Treslumen
30. März um 21:54
-
@TippuFisalSheriff-Check Developer.android.com/reference/kotlin/androidx/compose/ui/…. Es ist nur
Size(0.0f, 0.0f)
– Gabriele Mariotti
29. Mai um 12:38 Uhr
Folgendes habe ich getan, um die gleiche Breite wie das Textfeld zu erreichen: Gabrieles Antwort kopieren und ändern.
var expanded by remember { mutableStateOf(false) }
val suggestions = listOf("Item1","Item2","Item3")
var selectedText by remember { mutableStateOf("") }
var dropDownWidth by remember { mutableStateOf(0) }
val icon = if (expanded)
Icons.Filled.....
else
Icons.Filled.ArrowDropDown
Column() {
OutlinedTextField(
value = selectedText,
onValueChange = { selectedText = it },
modifier = Modifier.fillMaxWidth()
.onSizeChanged {
dropDownWidth = it.width
},
label = {Text("Label")},
trailingIcon = {
Icon(icon,"contentDescription", Modifier.clickable { expanded = !expanded })
}
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(with(LocalDensity.current){dropDownWidth.toDp()})
) {
suggestions.forEach { label ->
DropdownMenuItem(onClick = {
selectedText = label
}) {
Text(text = label)
}
}
}
}
Salomon BRYS
Hier ist meine Version. Ich habe dies erreicht, ohne a zu verwenden TextField
(also keine Tastatur). Es gibt eine „normale“ und eine „umrissene“ Version.
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.ZeroCornerSize
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
// ExposedDropDownMenu will be added in Jetpack Compose 1.1.0.
// This is a reimplementation while waiting.
// See https://stackoverflow.com/questions/67111020/exposed-drop-down-menu-for-jetpack-compose/6904285
@Composable
fun SimpleExposedDropDownMenu(
values: List<String>,
selectedIndex: Int,
onChange: (Int) -> Unit,
label: @Composable () -> Unit,
modifier: Modifier,
backgroundColor: Color = MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.BackgroundOpacity),
shape: Shape = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize)
) {
SimpleExposedDropDownMenuImpl(
values = values,
selectedIndex = selectedIndex,
onChange = onChange,
label = label,
modifier = modifier,
backgroundColor = backgroundColor,
shape = shape,
decorator = { color, width, content ->
Box(
Modifier
.drawBehind {
val strokeWidth = width.value * density
val y = size.height - strokeWidth / 2
drawLine(
color,
Offset(0f, y),
Offset(size.width, y),
strokeWidth
)
}
) {
content()
}
}
)
}
@Composable
fun SimpleOutlinedExposedDropDownMenu(
values: List<String>,
selectedIndex: Int,
onChange: (Int) -> Unit,
label: @Composable () -> Unit,
modifier: Modifier,
backgroundColor: Color = MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.BackgroundOpacity),
shape: Shape = MaterialTheme.shapes.small
) {
SimpleExposedDropDownMenuImpl(
values = values,
selectedIndex = selectedIndex,
onChange = onChange,
label = label,
modifier = modifier,
backgroundColor = backgroundColor,
shape = shape,
decorator = { color, width, content ->
Box(
Modifier
.border(width, color, shape)
) {
content()
}
}
)
}
@Composable
private fun SimpleExposedDropDownMenuImpl(
values: List<String>,
selectedIndex: Int,
onChange: (Int) -> Unit,
label: @Composable () -> Unit,
modifier: Modifier,
backgroundColor: Color,
shape: Shape,
decorator: @Composable (Color, Dp, @Composable () -> Unit) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
var textfieldSize by remember { mutableStateOf(Size.Zero) }
val indicatorColor =
if (expanded) MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high)
else MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.UnfocusedIndicatorLineOpacity)
val indicatorWidth = (if (expanded) 2 else 1).dp
val labelColor =
if (expanded) MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high)
else MaterialTheme.colors.onSurface.copy(ContentAlpha.medium)
val trailingIconColor = MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.IconOpacity)
val rotation: Float by animateFloatAsState(if (expanded) 180f else 0f)
val focusManager = LocalFocusManager.current
Column(modifier = modifier.width(IntrinsicSize.Min)) {
decorator(indicatorColor, indicatorWidth) {
Box(
Modifier
.fillMaxWidth()
.background(color = backgroundColor, shape = shape)
.onGloballyPositioned { textfieldSize = it.size.toSize() }
.clip(shape)
.clickable {
expanded = !expanded
focusManager.clearFocus()
}
.padding(start = 16.dp, end = 12.dp, top = 7.dp, bottom = 10.dp)
) {
Column(Modifier.padding(end = 32.dp)) {
ProvideTextStyle(value = MaterialTheme.typography.caption.copy(color = labelColor)) {
label()
}
Text(
text = values[selectedIndex],
modifier = Modifier.padding(top = 1.dp)
)
}
Icon(
imageVector = Icons.Filled.ExpandMore,
contentDescription = "Change",
tint = trailingIconColor,
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(top = 4.dp)
.rotate(rotation)
)
}
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(with(LocalDensity.current) { textfieldSize.width.toDp() })
) {
values.forEachIndexed { i, v ->
val scope = rememberCoroutineScope()
DropdownMenuItem(
onClick = {
onChange(i)
scope.launch {
delay(150)
expanded = false
}
}
) {
Text(v)
}
}
}
}
}
-
Vielen Dank, dass Sie dies geteilt haben. Dieses sieht genau so aus und funktioniert, wie ich es von einem ExposedDropdown erwarte. Das einzige, was ich ändern musste, war die Verwendung
Icons.Filled.ArrowDropDown
anstattIcons.Filled.ExpandMore
.– ice_chrysler
11. Okt. 2021 um 10:24
Wenn Sie verwenden material3
und eine neuere Version von Compose (dies funktioniert für v1.3.1
), Die DropdownMenuItem
hat sich leicht verändert. Text muss jetzt eine Eigenschaft sein (und nicht eine @Composable
).
Sie müssen sich weiterhin für die experimentelle API anmelden. @OptIn(ExperimentalMaterial3Api::class)
.
Dieses Beispiel befindet sich in der androidx.compose.material3 Dokumentation.
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
val options = listOf("Option 1", "Option 2", "Option 3", "Option 4", "Option 5")
var expanded by remember { mutableStateOf(false) }
var selectedOptionText by remember { mutableStateOf(options[0]) }
// We want to react on tap/press on TextField to show menu
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded },
) {
TextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
modifier = Modifier.menuAnchor(),
readOnly = true,
value = selectedOptionText,
onValueChange = {},
label = { Text("Label") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.textFieldColors(),
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
options.forEach { selectionOption ->
DropdownMenuItem(
text = { Text(selectionOption) },
onClick = {
selectedOptionText = selectionOption
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
Als ich dies auf die „alte Art“ machte, traten bei mir die folgenden Fehler auf Text(text = selectionOption)
Linie:
No value passed for parameter 'text'
Type mismatch: inferred type is () -> Unit but MutableInteractionSource was expected
@Composable invocations can only happen from the context of a @Composable function
Arpit Patel
Ein paar Änderungen an der Antwort von @Gabriele Mariotti. Ein Benutzer kann ein Gliederungstextfeld auswählen und aus einer Option auswählen. Die Option verschwindet, sobald der Benutzer eine Option auswählt.
@Composable
fun DropDownMenu(optionList: List<String>,label:String,) {
var expanded by remember { mutableStateOf(false) }
var selectedText by remember { mutableStateOf("") }
var textfieldSize by remember { mutableStateOf(Size.Zero) }
val icon = if (expanded)
Icons.Filled.KeyboardArrowUp
else
Icons.Filled.KeyboardArrowDown
Column() {
OutlinedTextField(
value = selectedText,
onValueChange = { selectedText = it },
enabled = false,
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
//This value is used to assign to the DropDown the same width
textfieldSize = coordinates.size.toSize()
}
.clickable { expanded = !expanded },
label = { Text(label) },
trailingIcon = {
Icon(icon, "Drop Down Icon",
Modifier.clickable { expanded = !expanded })
}
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(with(LocalDensity.current) { textfieldSize.width.toDp() })
) {
optionList.forEach { label ->
DropdownMenuItem(onClick = {
selectedText = label
expanded = !expanded
}) {
Text(text = label)
}
}
}
}
}
Zusätzlich zu dem, was hier geschrieben wurde, könnte ich für jemanden nützlich sein und für meine persönliche Notiz für die nächste Verwendung habe ich diese Dropdown-Menü-Funktionskomponente realisiert, die BasicTextField ohne Dekoration und ohne Standardauffüllung und ohne Pfeilsymbol verwendet , wobei der Text des ausgewählten Elements nach rechts ausgerichtet ist (.End), wobei die maximale Textbreite (.fillMaxWidth()) mit einer einzelnen Zeile in der Liste gefüllt wird.
data class DropDownMenuParameter(
var options: List<String>,
var expanded: Boolean,
var selectedOptionText: String,
var backgroundColor: Color
)
@ExperimentalMaterialApi
@Composable
fun DropDownMenuComponent(params: DropDownMenuParameter) {
var expanded by remember { mutableStateOf(params.expanded) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
BasicTextField(
modifier = Modifier
.background(params.backgroundColor)
.fillMaxWidth(),
readOnly = true,
value = params.selectedOptionText,
onValueChange = { },
textStyle = TextStyle(
color = Color.White,
textAlign = TextAlign.End,
fontSize = 16.sp,
),
singleLine = true
)
ExposedDropdownMenu(
modifier = Modifier
.background(params.backgroundColor),
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
params.options.forEach { selectionOption ->
DropdownMenuItem(
modifier = Modifier
.background(params.backgroundColor),
onClick = {
params.selectedOptionText = selectionOption
expanded = false
},
) {
Text(
text = selectionOption,
color = Color.White,
)
}
}
}
}
}
Meine Verwendung:
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
@Composable
fun SubscribeSubscriptionDetails(selectedSubscription : Subscription){
val categoryOptions = listOf("Entertainment", "Gaming", "Business", "Utility", "Music", "Food & Drink", "Health & Fitness", "Bank", "Transport", "Education", "Insurance", "News")
val categoryExpanded by rememberSaveable { mutableStateOf(false) }
val categorySelectedOptionText
by rememberSaveable { mutableStateOf(selectedSubscription.category) }
val categoryDropDownMenuPar by remember {
mutableStateOf(
DropDownMenuParameter(
options = categoryOptions,
expanded = categoryExpanded,
selectedOptionText = categorySelectedOptionText,
backgroundColor = serviceColorDecoded
)
)
}
// ....
Row { // categoria
Text(
modifier = Modifier
.padding(textMargin_24, 0.dp, 0.dp, 0.dp)
.weight(0.5f),
text = "Categoria",
fontWeight = FontWeight.Bold,
color = Color.White,
textAlign = TextAlign.Left,
fontSize = 16.sp,
)
Row(
modifier = Modifier
.padding(0.dp, 0.dp, 24.dp, 0.dp)
.weight(0.5f),
horizontalArrangement = Arrangement.End
){
DropDownMenuComponent(categoryDropDownMenuPar)
}
}
// .....
}
um den Wert nach der Auswahl abzurufen: CategoryDropDownMenuPar.selectedOptionText