Group 8.png

Objective

Recreate a similar Icon Button with a Menu from the Taskose UI design kit I acquired, utilizing Android Jetpack Compose.

1*ykZ8nDZ-JgxC-v6JazrWOw.png

Environment

Implementation

Download the needed icons here, and add them to res/drawable directory.

// Your package...

import android.widget.Toast
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
// import YOUR_PACKAGE_PATH.R

data class MenuItem(
    val drawableId: Int? = null,
    val label: String,
    val distinguishedColor: Color? = null,
    val testTag: String = "",
    val onClick: () -> Unit
)

data class IconButtonDropdownMenuStyle(
    val menuItemColor: Color = Color(0xFF31394F).copy(alpha = 0.8f),
    val menuItemTextStyle: TextStyle = TextStyle(
        fontWeight = FontWeight.Normal,
        fontSize = 14.sp,
        color = menuItemColor
    ),
    val menuItemHeight: Dp = 40.dp,
    val menuItemHorizontalPadding: Dp = 16.dp,
)

@Composable
fun IconButtonDropdownMenu(
    modifier: Modifier = Modifier,
    style: IconButtonDropdownMenuStyle = IconButtonDropdownMenuStyle(),
    menuItems: List<MenuItem>,
    testTag: String = "",
    iconDrawableId: Int = R.drawable.ic_more,
) {
    val expanded = remember { mutableStateOf(false) }
    Column(modifier = modifier) {
        IconButton(
            onClick = {
                expanded.value = true
            },
            modifier = Modifier.testTag(testTag)
        ) {
            Icon(
                imageVector = ImageVector.vectorResource(id = iconDrawableId),
                contentDescription = null
            )
        }
        DropdownMenu(
            expanded = expanded.value,
            onDismissRequest = { expanded.value = false }
        ) {
            menuItems.forEach { menuItem ->
                DropdownMenuItem(
                    text = {
                        val textStyle = if (menuItem.distinguishedColor != null) {
                            style.menuItemTextStyle.copy(color = menuItem.distinguishedColor)
                        } else {
                            style.menuItemTextStyle
                        }
                        Text(
                            menuItem.label,
                            style = textStyle,
                            modifier = Modifier.testTag(menuItem.testTag)
                        )
                    },
                    onClick = {
                        expanded.value = false
                        menuItem.onClick()
                    },
                    // Use the 'let' function to execute the lambda only if
                    // 'drawableId' is not null.
                    // This ensures that the 'leadingIcon' will only be added to
                    // the DropdownMenuItem if an icon is provided.
                    // Otherwise, 'leadingIcon' is null,
                    // and no space is reserved for it in the layout.
                    leadingIcon = menuItem.drawableId?.let { id ->
                        val iconTint = menuItem.distinguishedColor ?: style.menuItemColor
                        {
                            Icon(
                                imageVector = ImageVector.vectorResource(id = id),
                                contentDescription = null,
                                tint = iconTint
                            )
                        }
                    },
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(style.menuItemHeight),
                    contentPadding = PaddingValues(horizontal = style.menuItemHorizontalPadding)
                )
            }
        }
    }
}

@Preview
@Composable
fun IconButtonDropdownMenuPreview() {
    val context = LocalContext.current
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        IconButtonDropdownMenu(
            menuItems = listOf(
                MenuItem(
                    R.drawable.ic_calendar,
                    label = "Calendar",
                    distinguishedColor = Color.Red
                ) {
                    Toast.makeText(context, "Calendar", Toast.LENGTH_SHORT).show()
                },
                MenuItem(R.drawable.ic_done, label = "Check") {
                    Toast.makeText(context, "Check", Toast.LENGTH_SHORT).show()
                },
                MenuItem(
                    label = "Add Project",
                    distinguishedColor = Color.Blue
                ) {
                    Toast.makeText(context, "Add Project", Toast.LENGTH_SHORT).show()
                },
            )
        )
    }
}

Preview Result

untitled.gif

Explore More

For developers eager to deepen their understanding or explore more about modern Android development, a wealth of resources and guides await.

Android Compose Tutorials Library

The Ultimate Android Development Career Guide

If you've found this resource helpful, consider sharing your support through claps or a follow, and stay tuned for more insights into the evolving landscape of Android development. See you the next time!