25

I am trying to rewrite my project UI using Jetpack compose. Any idea to add popup menu using jetpack compose in android?. like this one below.

like this one

I tried to implement it using Stack() layout but the results are not up to the mark.

@Composable fun LiveDataComponentList(productList: List<Product>) { AdapterList(data = productList) { product -> Stack() { Clickable(onClick = { PopupState.toggleOwner(product) }) { Card(...) {...} if (PopupState.owner == product) { Surface(color = Color.Gray,modifier = Modifier.gravity(Alignment.TopEnd) + Modifier.padding(12.dp)) { Column() { Text("menu 1") Text("menu 2") Text("menu 3") Text("menu 4") Text("menu 5") } } } } } } 

and PopupState is

@Model object PopupState { var owner:Product?=null fun toggleOwner(item:Product) { if(owner==item) owner=null else owner=item } } 

result is

screenshot

4 Answers 4

46

You can use the DropdownMenu.

Something like:

var expanded by remember { mutableStateOf(false) } DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false } ) { DropdownMenuItem( text = { Text("Refresh") }, onClick = { /* Handle refresh! */ } ) DropdownMenuItem( text = { Text("Settings") }, onClick = { /* Handle settings! */ } ) Divider() DropdownMenuItem( text = { Text("Send Feedback") }, onClick = { /* Handle send feedback! */ } ) } 

It works with M3. With M2 you have to use:

androidx.compose.material.DropdownMenuItem( onClick = { expanded = false } ) { Text("All Accounts") } 

enter image description here

About the position, as explained in the documentation:

A DropdownMenu behaves similarly to a Popup, and will use the position of the parent layout to position itself on screen. Commonly a DropdownMenu will be placed in a Box with a sibling that will be used as the 'anchor'.

Example:

Box( modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.TopStart) ){ IconButton(onClick = { expanded = true }) { Icon( Icons.Default.MoreVert, contentDescription = "Localized description" ) } DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false } ) { //... } 
Sign up to request clarification or add additional context in comments.

4 Comments

How to set it's position around a button or any other view?
@TalhaAkbar Check the updated answer. It uses the position of the parent layout to position itself on screen.
@GabrieleMariotti I'm not seeing the part where you position the menu based on the position of the parent layout
@MKiperszmid Commonly a DropdownMenu will be placed in a Box with a sibling that will be used as the 'anchor'. In the example the DropdownMenu uses the IconButton as 'anchor'
12

Since DropDownPopup was removed, I implemented one using DropDownMenu instead like this:

PopupMenu:

@Composable fun PopupMenu( menuItems: List<String>, onClickCallbacks: List<() -> Unit>, showMenu: Boolean, onDismiss: () -> Unit, toggle: @Composable () -> Unit, ) { DropdownMenu( toggle = toggle, expanded = showMenu, onDismissRequest = { onDismiss() }, ) { menuItems.forEachIndexed { index, item -> DropdownMenuItem(onClick = { onDismiss() onClickCallbacks[index] }) { Text(text = item) } } } } 

Toggle (thing to long click on to trigger PopupMenu):

@Preview @Composable fun Toggle() { var showMenu by remember { mutableStateOf(false) } PopupMenu( menuItems = listOf("Delete"), onClickCallbacks = listOf { println("Deleted") }, showMenu = showMenu, onDismiss = { showMenu = false }) { Text( modifier = Modifier.clickable(onClick = {}, onLongClick = { showMenu = true }), text = "Long click here", ) } } 

2 Comments

Is It possible to add anything other than just a Text()? Crashes when adding an ImageView for example..
How can this be attached to a button?
3

After some research I found a solution to this, the key component is DropdownPopup

@Composable fun LiveDataComponentList(productList: List<Product>) { AdapterList(data = productList) { product -> Clickable(onClick = { PopupState.toggleOwner(product) }) { Card(...) {...} } if (PopupState.owner == product) { DropdownPopup(dropDownAlignment = DropDownAlignment.End) { Surface( shape = RoundedCornerShape(4.dp), elevation = 16.dp, color = Color.White, modifier = Modifier.gravity(Alignment.End)+ Modifier.padding(end = 10.dp) ) { Column(modifier = Modifier.padding(10.dp)) { MenuItem(text ="Edit", onClick = {}) MenuItem(text = "Delete", onClick = {}) MenuItem(text = "Details", onClick = {}) } } } } } } @Composable fun MenuItem(text: String, onClick: () -> Unit) { Clickable(onClick = onClick, modifier = Modifier.padding(6.dp)) { Text(text = text, style = MaterialTheme.typography.subtitle1) } } 

This solution works fine with compose version dev10

4 Comments

DropDownPopup was removed on: August 5, 2020 androidx.compose.ui:ui-*:0.1.0-dev16 Removed dropdownPopup. (I00430) developer.android.com/jetpack/androidx/releases/…
removed and replaced by?
@Jonathan perhaps replaced by DropdownMenu
How can we set alignment with DropdownMenu?
1

For My use case I created a Icon button which has pop up menu and that can be used where pop menu is needed.

@Composable fun PopUpMenuButton( options: List<PopUpMenuItem>, action: (String) -> Unit, iconTint: Color = Color.Black, modifier: Modifier ) { var expanded by remember { mutableStateOf(false) } Column { Box(modifier = Modifier.size(24.dp)) { IconButton(onClick = { expanded = !expanded }) { Icon( painter = painterResource(id = R.drawable.ic_dots), contentDescription = null, modifier = Modifier.wrapContentSize(), tint = iconTint ) } } Box(modifier = modifier) { DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, modifier = Modifier .widthIn(min = 120.dp, max = 240.dp) .background(MaterialTheme.colors.background) ) { options.forEachIndexed { _, item -> DropdownMenuItem(onClick = { expanded = false action(item.id) }) { Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Icon( painterResource(id = item.icon), contentDescription = null, tint = iconTint, ) Spacer(modifier = Modifier.width(8.dp)) Text( text = item.label, style = MaterialTheme.typography.body1, overflow = TextOverflow.Ellipsis ) } } if (item.hasBottomDivider) { Divider() } } } } } } 

Then I created a simple data class for defining menu item

data class PopUpMenuItem( val id: String, val label: String, val icon: Int, val hasBottomDivider: Boolean = false, ) 

Then at the calling side I simply use this button like this

 PopUpMenuButton( modifier = Modifier.wrapContentSize(), options = PopMenuOptionsProvider.sectionCardMenu, iconTint = MaterialTheme.extendedColor.regularGray, action = { menuId -> onSectionMenuAction(menuId) } ) 

It can be further refactored to make it more extensible, but this worked for me.

1 Comment

Having trouble getting this code to compile. DropdownMenuItem() needs another parameter (probably added since your code was written). Could you list the imports you used?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.