Jetpack Compose, Google's modern toolkit for building native UI, has transformed Android development by offering a declarative approach. Among its various features, implementing a Floating Action Button (FAB) and extending it into a dynamic menu is a task every Android developer should master. In this blog post, we will explore advanced techniques to create a FAB menu using Jetpack Compose, leveraging best practices and optimizing for performance and user experience.
Why Use a FAB Menu?
The Floating Action Button is a widely recognized UI pattern that represents a primary action on a screen. Extending it into a FAB menu can:
Improve navigation: Allow access to multiple related actions from a single entry point.
Save screen space: Keep the UI clean and uncluttered while providing quick access to key features.
Enhance user experience: Add interactive and visually appealing animations that delight users.
Jetpack Compose simplifies the creation of such components, making it easier to build, animate, and manage the state of a FAB menu.
Setting Up Your Jetpack Compose Project
Before diving into the implementation, ensure your project is ready to use Jetpack Compose. Add the necessary dependencies to your build.gradle
file:
// Module-level build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
dependencies {
implementation "androidx.compose.ui:ui:1.x.x"
implementation "androidx.compose.material:material:1.x.x"
implementation "androidx.compose.animation:animation:1.x.x"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.x.x"
implementation "androidx.activity:activity-compose:1.x.x"
}
Update your minSdk
version to at least 21
as Compose requires it.
Building the FAB Menu
Step 1: Define the FAB Menu State
A FAB menu typically toggles between open and closed states. Use a MutableState
to track this state:
@Composable
fun rememberFabMenuState(): MutableState<Boolean> {
return remember { mutableStateOf(false) }
}
Step 2: Create the Main FAB
The primary FAB acts as the trigger for the menu. Use the FloatingActionButton
component and animate its icon based on the menu state:
@Composable
fun MainFab(isMenuOpen: Boolean, onClick: () -> Unit) {
FloatingActionButton(onClick = onClick) {
Icon(
imageVector = if (isMenuOpen) Icons.Default.Close else Icons.Default.Add,
contentDescription = null
)
}
}
Step 3: Design the FAB Menu Items
Define the menu items as a list of composables, each representing a distinct action. Use animations to position and reveal them:
@Composable
fun FabMenuItems(isMenuOpen: Boolean, menuItems: List<Pair<String, () -> Unit>>) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(bottom = 72.dp) // Adjust to position items correctly
) {
menuItems.forEachIndexed { index, item ->
AnimatedVisibility(
visible = isMenuOpen,
enter = slideInVertically(initialOffsetY = { it * (index + 1) }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it * (index + 1) }) + fadeOut()
) {
FloatingActionButton(onClick = item.second) {
Text(item.first)
}
}
}
}
}
Step 4: Combine the Components
Bring everything together in a parent composable:
@Composable
fun FabMenu() {
val isMenuOpen = rememberFabMenuState()
Box(modifier = Modifier.fillMaxSize()) {
// Place FAB menu items
FabMenuItems(
isMenuOpen = isMenuOpen.value,
menuItems = listOf(
"Action 1" to { /* Action 1 logic */ },
"Action 2" to { /* Action 2 logic */ },
"Action 3" to { /* Action 3 logic */ }
)
)
// Place main FAB at the bottom-right corner
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.BottomEnd
) {
MainFab(isMenuOpen = isMenuOpen.value) {
isMenuOpen.value = !isMenuOpen.value
}
}
}
}
Advanced Techniques and Best Practices
1. Use Motion and Animations Thoughtfully
Jetpack Compose’s animation APIs like AnimatedVisibility
, animateFloatAsState
, and Modifier.animateContentSize()
allow smooth transitions. Avoid excessive animations that may slow down the app.
2. Optimize for Accessibility
Add meaningful
contentDescription
to all icons and buttons.Ensure keyboard navigation and screen readers work seamlessly.
FloatingActionButton(
onClick = onClick,
contentDescription = if (isMenuOpen) "Close menu" else "Open menu"
) {
Icon(
imageVector = if (isMenuOpen) Icons.Default.Close else Icons.Default.Add,
contentDescription = null
)
}
3. Maintain Code Reusability
Extract reusable components and avoid hardcoding values. Use @Composable
functions to encapsulate UI logic and parameters for high configurability.
4. Handle Performance Efficiently
Use
LazyColumn
if the menu has a large number of items.Avoid overloading recompositions by properly scoping
remember
.
Testing Your FAB Menu
Testing composables is essential for maintaining robust UI:
Write Unit Tests
Use Compose testing libraries to validate UI behavior:
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testFabMenuToggle() {
composeTestRule.setContent {
FabMenu()
}
// Assert initial state
composeTestRule.onNodeWithContentDescription("Open menu").assertExists()
// Perform click and assert new state
composeTestRule.onNodeWithContentDescription("Open menu").performClick()
composeTestRule.onNodeWithContentDescription("Close menu").assertExists()
}
Conclusion
Building a Floating Action Button menu in Jetpack Compose is both intuitive and powerful. By leveraging state management, animations, and Compose’s modern UI toolkit, you can create engaging and user-friendly FAB menus for your Android applications. Implement the best practices outlined here to ensure performance, accessibility, and scalability in your apps.
Feel free to explore and extend this example to fit your app’s unique requirements. Happy coding!