dropdown list on button click compose

5k Views Asked by At

How to create a dropdown menu items on a button click. In Jetpack compose?

Like here but for buttons :

      DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false },
            toggle = iconButton,
            dropdownOffset = Position(24.dp, 0.dp),
            toggleModifier = modifier
        ) {
            options.forEach {
                DropdownMenuItem(onClick = {}) {
                    Text(it)
                }
            }
        }
3

There are 3 best solutions below

0
On

You can use something like:

   var expanded by remember { mutableStateOf(false) }

   Button(onClick = { expanded = true }){
        Text ("...")
    }
    DropdownMenu(
        expanded = expanded,
        onDismissRequest = { expanded = false },
        //....
    ) {
        items.forEachIndexed { index, s ->
            //....
        }
    }
0
On

you can create a dropdown list in compose by using this

list : list you want to show

label : label is the hint to show in the textview

default : to set default value in textview

validateInput = you can validate the input by changing the validateInput state to true on the button clicked and handle it accordingly

fun dropdownList(
list: List<String>,
label: String,
defaultValue: String = "",
validateInput: Boolean
): String {

var expanded by remember { mutableStateOf(false) }
var selectedText by remember { mutableStateOf(defaultValue) }
var textFieldSize by remember { mutableStateOf(Size.Zero) }
var isError by remember { mutableStateOf(false) }

if (validateInput && selectedText.isEmpty())
    isError = true

val icon = if (expanded)
    Icons.Filled.ArrowDropUp
else
    Icons.Filled.ArrowDropDown


Column(modifier = Modifier.padding(bottom = 2.dp, top = 2.dp)) {
    OutlinedTextField(
        value = selectedText,
        onValueChange = {
            selectedText = it
        },
        modifier = Modifier
            .fillMaxWidth()
            .onGloballyPositioned { coordinates ->
                textFieldSize = coordinates.size.toSize()
            },
        label = { Text(label) },
        trailingIcon = {
            Icon(icon, "contentDescription",
                Modifier.clickable { expanded = !expanded })
        },
        isError = isError
    )
    DropdownMenu(
        expanded = expanded,
        onDismissRequest = { expanded = false },
        modifier = Modifier
            .width(with(LocalDensity.current) { textFieldSize.width.toDp() })
    ) {
        list.forEach { label ->
            DropdownMenuItem(onClick = {
                selectedText = label
                expanded = false
            }) {
                Text(text = label)
            }
        }
    }
    if (isError) {
        Text(
            text = "$label can't be empty",
            color = Color.Red,
            textAlign = TextAlign.End,
            modifier = Modifier.fillMaxWidth()
        )
    }
}
return selectedText
}

Github gist link DropdownList.kt

0
On

The previous answer is correct, but the key part is missing. Both, DropdownMenu and the button that opens it suppose to be wrapped in Box. Only this way the opening button will be used as an anchor for the menu. This is my version:

@Composable
fun DropdownMenu(
    colorSelected: Color = scColors.primary,
    colorBackground: Color = scColors.onSurface,
    expanded: Boolean,
    selectedIndex: Int,
    items: List<String>,
    onSelect: (Int) -> Unit,
    onDismissRequest: () -> Unit,
    content: @Composable () -> Unit
) {
    Box {
        content()
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = onDismissRequest,
            modifier = Modifier
                .height(300.dp)
                .fillMaxWidth()
                .background(
                    color = colorBackground,
                    shape = RoundedCornerShape(16.dp)
                )
        ) {
            items.forEachIndexed { index, s ->
                if (selectedIndex == index) {
                    DropdownMenuItem(
                        modifier = Modifier
                            .fillMaxWidth()
                            .background(
                                color = colorSelected,
                                shape = RoundedCornerShape(16.dp)
                            ),
                        onClick = { onSelect(index) }
                    ) {
                        Text(
                            text = s,
                            color = Color.Black,
                            textAlign = TextAlign.Center,
                            modifier = Modifier.fillMaxWidth()
                        )
                    }
                } else {
                    DropdownMenuItem(
                        modifier = Modifier.fillMaxWidth(),
                        onClick = { onSelect(index) }
                    ) {
                        Text(
                            text = s,
                            color = Color.DarkGray,
                            textAlign = TextAlign.Center,
                            modifier = Modifier.fillMaxWidth()
                        )
                    }
                }
            }
        }
    }
}

And, then a DropdownMenu accepts the opening anchor button as a content:

val items = listOf(
    "English",
    "Russian",
    "Spanish",
    "French",
    "German",
    "Hebrew"
)

@Preview
@Composable
fun TestDropdownMenu() {
    var expanded by remember { mutableStateOf(false) }

    var selectedIndex by remember { mutableStateOf(0) }
    val buttonTitle = items[selectedIndex]
    DropdownMenu(
        colorSelected = scColors.onSurface,
        colorBackground = scColors.primary,
        expanded = expanded,
        selectedIndex = selectedIndex,
        items = items,
        onSelect = { index ->
            selectedIndex = index
            expanded = false
        },
        onDismissRequest = {
            expanded = false
        }) {

        Button(
            onClick = {
                expanded = true
            }
        ) {
            Text(
                text = buttonTitle,
                color = Color.Black,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis
            )
        }
    }
}