Blog Infos
Author
Published
Topics
, , ,
Published
Transform Android UI with Toolbars, FlexibleBottomAppBar and FloatingActionButtonMenu

Generated by gemini.google.com

Material Design, continues to evolve, with its latest iteration, Material 3, introducing the concept of “Expressive Design”. As discussed in the first part of this series, expressive design aims to infuse UIs with a more human touch, leveraging richer color palettes, diverse shapes, size variations, fluid animations, and strategic container usage to create intuitive and delightful user experiences.

In the first article of this series, we explored how Material 3 enhances Android UI through new components like LoadingIndicatorSplitButtonLayout, and ButtonGroup. Now, in Part 2, we’ll dive into two more powerful elements that embody Expressive Design: ToolbarsFlexibleBottomAppBar and the FloatingActionButtonMenu. These components are crucial for navigation, contextual actions, and adding a layer of visual sophistication to your Android applications. All the examples discussed here are available in my GitHub repository.

Getting Started with Dependencies

To ensure you can leverage these latest Material 3 features, verify that your project’s gradle file is updated with the necessary dependencies, similar to how we updated the material3 version in the previous article:

[versions]
material3 = "1.4.0-alpha14"
[libraries]
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3"
Toolbars

Toolbars, in Material Design 3, are fundamental components of an Android application’s UI. They serve as a consistent area for branding, navigation, and key actions. The new toolbars are no longer just functional, they are designed to be more expressive, adapting dynamically to content and user interaction.

Material 3 introduces a range of toolbars composables, each offering distinct visual characteristics and behaviors that align with Expressive Design principles. We will see one by one the new possibilities.

VerticalFloatingToolbar (Expandible)

Material 3 introduces the concept of a VerticalFloatingToolbar. This component is a highly expressive and adaptable toolbar that can be positioned vertically, typically on the side of the screen. Its “expandable” nature allows it to dynamically show or hide content based on user interaction or scroll behavior, providing a clean UI when collapsed and rich functionality when expanded. This makes it ideal for contextual actions or secondary navigation that doesn’t need to be persistently visible.

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun ExpandableVerticalFloatingToolbarSample() {
    var expanded by rememberSaveable { mutableStateOf(true) }
    Scaffold(
        content = { innerPadding ->
            Box(Modifier.padding(innerPadding)) {
                LazyColumn(
                    modifier =
                        Modifier.floatingToolbarVerticalNestedScroll(
                            expanded = expanded,
                            onExpand = { expanded = true },
                            onCollapse = { expanded = false },
                        ),
                    state = rememberLazyListState(),
                    contentPadding = innerPadding,
                    verticalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    val list = (0..75).map { it.toString() }
                    items(count = list.size) {
                        Text(
                            text = list[it],
                            style = MaterialTheme.typography.bodyLarge,
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(horizontal = 16.dp)
                        )
                    }
                }
                VerticalFloatingToolbar(
                    modifier = Modifier
                        .align(Alignment.CenterEnd)
                        .offset(x = -ScreenOffset),
                    expanded = expanded,
                    leadingContent = {
                        IconButton(
                            onClick = {}
                        ) {
                            Icon(
                                imageVector = Icons.Filled.MoreVert,
                                contentDescription = null
                            )
                        }
                    },
                    trailingContent = {
                        AppBarColumn(
                            overflowIndicator = { menuState ->
                                IconButton(
                                    onClick = {
                                        if (menuState.isExpanded) {
                                            menuState.dismiss()
                                        } else {
                                            menuState.show()
                                        }
                                    }
                                ) {
                                    Icon(
                                        imageVector = Icons.Filled.MoreVert,
                                        contentDescription = ""
                                    )
                                }
                            }
                        ) {
                            clickableItem(
                                onClick = {},
                                icon = {
                                    Icon(
                                        Icons.Filled.Download,
                                        contentDescription = ""
                                    )
                                },
                                label = "Download"
                            )

                            clickableItem(
                                onClick = {},
                                icon = {
                                    Icon(
                                        Icons.Filled.Settings,
                                        contentDescription = ""
                                    )
                                },
                                label = "Settings"
                            )

                        }
                    },
                    content = {
                        FilledIconButton(
                            modifier = Modifier.height(64.dp),
                            onClick = { }
                        ) {
                            Icon(Icons.Filled.Add, contentDescription = null)
                        }
                    },
                )
            }
        }
    )
}
HorizontalFloatingToolbar (wih FAB)

The HorizontalFloatingToolbar offers another expressive way to present actions, typically positioned at the bottom of the screen. The HorizontalFloatingToolbar float above content and often integrates seamlessly with a FloatingActionButton (FAB), providing a central primary action alongside a set of related secondary actions. This combination creates a visually appealing and highly functional component that responds to user interaction.

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun OverflowingHorizontalFloatingToolbarSample() {
    Scaffold(
        content = { innerPadding ->
            Box(Modifier.padding(innerPadding)) {
                LazyColumn(
                    state = rememberLazyListState(),
                    contentPadding = innerPadding,
                    verticalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    val list = (0..75).map { it.toString() }
                    items(count = list.size) {
                        Text(
                            text = list[it],
                            style = MaterialTheme.typography.bodyLarge,
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(horizontal = 16.dp)
                        )
                    }
                }
                HorizontalFloatingToolbar(
                    modifier = Modifier
                        .align(Alignment.BottomCenter)
                        .offset(y = -ScreenOffset),
                    expanded = true,
                    floatingActionButton = {
                        FloatingToolbarDefaults.VibrantFloatingActionButton(
                            onClick = { /* */ },
                        ) {
                            Icon(Icons.Filled.Add, contentDescription = null)
                        }
                    },
                    content = {
                        FilledIconButton(
                            modifier = Modifier.width(64.dp),
                            onClick = { /* doSomething() */ }
                        ) {
                            Icon(Icons.Filled.Add, contentDescription = null)
                        }
                        IconButton(
                            onClick = {}
                        ) {
                            Icon(
                                imageVector = Icons.Filled.MoreVert,
                                contentDescription = null
                            )
                        }
                        IconButton(
                            onClick = {}
                        ) {
                            Icon(
                                imageVector = Icons.Filled.Settings,
                                contentDescription = null
                            )
                        }
                    }
                )
            }
        }
    )
}
HorizontalFloatingToolbar (as Scaffold Fab)

Another nice way to use the HorizontalFloatingToolbar is to integrate it directly as the floatingActionButton of a Scaffold. This approach allows the toolbar to leverage Scaffold’s built-in positioning and behavior for the FAB, making it a cohesive part of the overall screen layout. It’s particularly useful when you want a set of actions that are always accessible at the bottom of the screen, potentially expanding and collapsing based on user interaction or scroll. Here’s an example demonstrating HorizontalFloatingToolbar used as a Scaffold FAB:

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun HorizontalFloatingToolbarAsScaffoldFabSample() {
    var expanded by rememberSaveable { mutableStateOf(true) }
    val vibrantColors = FloatingToolbarDefaults.vibrantFloatingToolbarColors()
    Scaffold(
        floatingActionButton = {
            HorizontalFloatingToolbar(
                expanded = expanded,
                floatingActionButton = {
                    FloatingToolbarDefaults.VibrantFloatingActionButton(
                        onClick = { expanded = !expanded }
                    ) {
                        Icon(Icons.Filled.Add, null)
                    }
                },
                colors = vibrantColors,
                content = {
                    IconButton(onClick = {}) {
                        Icon(Icons.Filled.Person, contentDescription = null)
                    }
                    IconButton(onClick = {}) {
                        Icon(Icons.Filled.Edit, contentDescription = null)
                    }
                    IconButton(onClick = {}) {
                        Icon(Icons.Filled.Favorite, contentDescription = null)
                    }
                    IconButton(onClick = {}) {
                        Icon(Icons.Filled.MoreVert, contentDescription = null)
                    }
                },
            )
        },
        floatingActionButtonPosition = FabPosition.End,
    ) { innerPadding ->
        Box(Modifier.padding(innerPadding)) {
            Column(
                Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 16.dp)
                    .then(
                        Modifier.floatingToolbarVerticalNestedScroll(
                            expanded = expanded,
                            onExpand = { expanded = true },
                            onCollapse = { expanded = false },
                        )
                    )
                    .verticalScroll(rememberScrollState())
            ) {
                Text(text = remember { LoremIpsum().values.first() })
            }
        }
    }
}
FlexibleBottomAppBar

Let’s move to another type of component. The FlexibleBottomAppBar is a new Material 3 component that provides a highly adaptable bottom app bar. Unlike traditional fixed bottom app bars, the FlexibleBottomAppBar can dynamically adjust its layout and visibility based on scroll behavior, offering a more expressive and less intrusive navigation and action area. It’s particularly useful for applications that require a dynamic bottom bar that can adapt to content scrolling, providing a fluid and engaging user experience. Here’s an example demonstrating a FlexibleBottomAppBar with exitAlwaysScrollBehavior:

@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Preview
@Composable
fun ExitAlwaysBottomAppBarFixedVibrant() {
    val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        bottomBar = {
            FlexibleBottomAppBar(
                horizontalArrangement = BottomAppBarDefaults.FlexibleFixedHorizontalArrangement,
                scrollBehavior = scrollBehavior,
                containerColor =
                    MaterialTheme.colorScheme.primaryContainer,
                content = {
                    IconButton(onClick = { }) {
                        Icon(
                            Icons.AutoMirrored.Filled.ArrowBack,
                            contentDescription = null
                        )
                    }
                    IconButton(onClick = { }) {
                        Icon(
                            Icons.AutoMirrored.Filled.ArrowForward,
                            contentDescription = null
                        )
                    }
                    FilledIconButton(
                        modifier = Modifier.width(56.dp),
                        onClick = { }
                    ) {
                        Icon(Icons.Filled.Add, contentDescription = null)
                    }
                    IconButton(onClick = { }) {
                        Icon(Icons.Filled.Check, contentDescription = null)
                    }
                    IconButton(onClick = { }) {
                        Icon(Icons.Filled.Edit, contentDescription = null)
                    }
                }
            )
        },
        content = { innerPadding ->
            LazyColumn(
                contentPadding = innerPadding,
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                val list = (0..75).map { it.toString() }
                items(count = list.size) {
                    Text(
                        text = list[it],
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(horizontal = 16.dp)
                    )
                }
            }
        }
    )
}
FloatingActionButtonMenu

Last, but not less important component for today, is the FloatingActionButtonMenu. It is a powerful Material 3 component that transforms a single FloatingActionButton (FAB) into an expandable menu, revealing a set of related, secondary actions. This pattern is particularly expressive as it provides contextual relevance and enhances user delight through subtle animations and organized actions. It’s a more integrated and visually appealing solution compared to a simple expanding FAB, as it’s designed specifically for this purpose within the Material 3 expressive design framework.

Here’s an example demonstrating a FloatingActionButtonMenu:

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview(showBackground = true)
@Composable
fun MyFloatingActionButtonMenu() {
    Box(Modifier.fillMaxSize()) {

        val items = listOf(
            Icons.AutoMirrored.Filled.Message to "Reply",
            Icons.Filled.People to "Reply all",
            Icons.Filled.Contacts to "Forward",
            Icons.Filled.Snooze to "Snooze",
            Icons.Filled.Archive to "Archive",
            Icons.AutoMirrored.Filled.Label to "Label",
        )

        var fabMenuExpanded by rememberSaveable { mutableStateOf(false) }


        FloatingActionButtonMenu(
            modifier = Modifier.align(Alignment.BottomEnd), expanded = fabMenuExpanded, button = {
                ToggleFloatingActionButton(
                    modifier = Modifier
                        .semantics {
                            traversalIndex = -1f
                            stateDescription = if (fabMenuExpanded) "Expanded" else "Collapsed"
                            contentDescription = "Toggle menu"
                        }
                        .animateFloatingActionButton(
                            visible = true, alignment = Alignment.BottomEnd
                        ),
                    checked = fabMenuExpanded,
                    onCheckedChange = { fabMenuExpanded = !fabMenuExpanded }) {
                    val imageVector by remember {
                        derivedStateOf {
                            if (checkedProgress > 0.5f) Icons.Filled.Close else Icons.Filled.Add
                        }
                    }
                    Icon(
                        painter = rememberVectorPainter(imageVector),
                        contentDescription = null,
                        modifier = Modifier.animateIcon({ checkedProgress })
                    )
                }
            }) {
            items.forEachIndexed { i, item ->
                FloatingActionButtonMenuItem(
                    modifier = Modifier.semantics {
                        isTraversalGroup = true
                        if (i == items.size - 1) {
                            customActions = listOf(
                                CustomAccessibilityAction(
                                    label = "Close menu", action = {
                                        fabMenuExpanded = false
                                        true
                                    })
                            )
                        }
                    },
                    onClick = { fabMenuExpanded = false },
                    icon = { Icon(item.first, contentDescription = null) },
                    text = { Text(text = item.second) },
                )
            }
        }
    }
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

Conclusion

Material 3’s Expressive Design paradigm is fundamentally about creating Android UIs that are more intuitive, engaging, and reflective of a user’s personal style. By embracing dynamic colors, adaptive shapes, and fluid animations, Material 3 empowers developers to craft experiences that go beyond mere utility.

The components explored in this article: the VerticalFloatingToolbarHorizontalFloatingToolbar (both with and as a Scaffold FAB), FlexibleBottomAppBar, and the FloatingActionButtonMenu are prime examples of this evolution. They offer dynamic adaptability, visual prominence, and provide clean yet expressive ways to manage contextual actions. These, alongside the LoadingIndicatorSplitButtonLayout, and ButtonGroup discussed in Part 1, provide a robust toolkit for building enhanced user experiences.

As developers, integrating these Material 3 features will enable us to design applications that are not only visually appealing but also more user-friendly and effective. The journey into Expressive Design is about crafting UIs that feel natural and responsive.

If you found this article interesting, feel free to follow me for more insightful content on Android development and Jetpack Compose. I regularly publish new articles on these topics. Don’t hesitate to share your comments or reach out to me on Bluesky or LinkedIn for further discussions.

Have a great day, and happy coding!

This article was previously published on proandroiddev.com.

Menu