Blog Infos
Author
Published
Topics
Published

Showing menu items in multiple food & drinks apps is one of the best use cases for multiple lists interaction, Let’s create a simple implementation with Jetpack Compose 🚀

We’ll cover all of the following points :

  • Show menu items list labeled by sections
  • Show sections list and highlight the selected one
  • On menu items list scroll, highlight the sections list with the displayed section
  • On click on section name, jump to the menu items list section position

 

 

Show menu items list labeled by sections

This is a simple list for all menu items labeled & indexed by sections title

  • itemsListState is a LazyListState which will be used to handle auto scroll which will be discussed later
  • onPostScroll used to detect a scroll action on the menu items list, called from nestedScroll modifier

 

@Composable
fun MenuItemsView(
menuSections: List<MenuSections>,
itemsListState: LazyListState,
onPostScroll: () -> Unit
) {
LazyColumn(
state = itemsListState,
modifier = Modifier
.padding()
.nestedScroll(object : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
): Offset {
onPostScroll()
return super.onPostScroll(consumed, available, source)
}
})
) {
menuSections.forEach { section ->
item {
Text(
modifier = Modifier.padding(10.dp),
text = section.title,
style = TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold),
color = Purple200
)
MenuItemView(section)
}
}
}
}

And this is the MenuItemView, just a simple view to show section items:

@Composable
fun MenuItemView(section: MenuSections) {
Column {
section.menuItems.forEach { menuItem ->
Column(Modifier.padding(10.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
text = menuItem.title,
style = TextStyle(
fontSize = 20.sp,
fontWeight = FontWeight.Bold
),
color = Color.DarkGray
)
Text(
text = menuItem.price,
style = TextStyle(
fontSize = 20.sp,
fontWeight = FontWeight.SemiBold
),
color = Color.DarkGray
)
}
Image(
modifier = Modifier.size(100.dp),
painter = painterResource(id = menuItem.imageID),
contentScale = ContentScale.FillWidth,
contentDescription = "Menu Item Image"
)
}
Divider(Modifier.padding(top = 20.dp))
}
}
}
}
view raw MenuItemView.kt hosted with ❤ by GitHub
Show sections list and highlight the selected one

This is a simple list to show sections titles in a row and used SectionTextView to show and highlight the selected section:

@Composable
fun MenuSectionsView(
selectedIndex: Int,
menuSections: List<MenuSections>,
sectionsListState: LazyListState,
onClick: (sectionIndex: Int) -> Unit
) {
LazyRow(
modifier = Modifier.padding(),
state = sectionsListState
) {
menuSections.forEachIndexed { i, section ->
item {
SectionTextView(
modifier = Modifier
.padding(horizontal = 10.dp)
.clickable { onClick(i) },
text = section.title,
isSelected = selectedIndex == i
)
}
}
}
}

SectionTextView will also show an underline to the selected section item, the underline width textWidth should always match the text size so it is measured on onGloballyPositioned modifier:

@Composable
fun SectionTextView(
modifier: Modifier = Modifier,
text: String,
isSelected: Boolean
) {
Column(modifier) {
var textWidth by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
Text(
modifier = Modifier.onGloballyPositioned {
textWidth = with(density) { it.size.width.toDp() } //update text width value according to the content size
},
text = text,
style = TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold),
color = if (isSelected) Purple200 else Color.DarkGray
)
//Show the text underline with animation
AnimatedVisibility(
visible = isSelected,
enter = expandHorizontally() + fadeIn(),
exit = shrinkHorizontally() + fadeOut()
) {
Box(
Modifier
.width(textWidth)
.padding(top = 15.dp)
.height(3.dp)
.background(Purple200)
) {}
}
}
}

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

No results found.

On menu items list scroll, highlight the sections list with the displayed section

The main parent view which will handle both lists interactions is MenuView

It will hold the current selection and handle onPostScroll event from the menu items list view

  • Update the selected section index
  • Scroll through the sections list to the selected section title position
@Composable
fun MenuView(
modifier: Modifier = Modifier,
menuSections: List<MenuSections> = FakeData.menuData
) {
val scope = rememberCoroutineScope()
...
...
MenuItemsView(
menuSections = menuSections,
itemsListState = itemsListState,
onPostScroll = {
val currentSectionIndex = itemsListState.firstVisibleItemIndex
if (selectedSectionIndex != currentSectionIndex) {
selectedSectionIndex = currentSectionIndex
scope.launch {
sectionsListState.animateScrollToItem(currentSectionIndex)
}
}
}
)
}
}
On click on section name, jump to the section menu items list position

MenuView will also handle section title click event

  • Update the selected section index
  • Scroll through the items list to the selected section position
  • Scroll through the sections list to the selected section title position
fun MenuView(
modifier: Modifier = Modifier,
menuSections: List<MenuSections> = FakeData.menuData
) {
val scope = rememberCoroutineScope()
...
...
MenuSectionsView(
selectedIndex = selectedSectionIndex,
menuSections = menuSections,
sectionsListState = sectionsListState,
onClick = { sectionIndex ->
selectedSectionIndex = sectionIndex
scope.launch {
sectionsListState.animateScrollToItem(sectionIndex)
itemsListState.animateScrollToItem(sectionIndex)
}
}
)
}
}

Finally the MenuView will be like this:

fun MenuView(
modifier: Modifier = Modifier,
menuSections: List<MenuSections> = FakeData.menuData
) {
val scope = rememberCoroutineScope()
val sectionsListState = rememberLazyListState()
val itemsListState = rememberLazyListState()
var selectedSectionIndex by remember { mutableStateOf(0) }
Column(modifier) {
Text(
modifier = Modifier.padding(
horizontal = 10.dp,
vertical = 30.dp
),
text = "Menu",
style = TextStyle(fontSize = 28.sp, fontWeight = FontWeight.Bold),
color = Color.DarkGray
)
MenuSectionsView(
selectedIndex = selectedSectionIndex,
menuSections = menuSections,
sectionsListState = sectionsListState,
onClick = { sectionIndex ->
selectedSectionIndex = sectionIndex
scope.launch {
sectionsListState.animateScrollToItem(sectionIndex)
itemsListState.animateScrollToItem(sectionIndex)
}
}
)
Divider()
MenuItemsView(
menuSections = menuSections,
itemsListState = itemsListState,
onPostScroll = {
val currentSectionIndex = itemsListState.firstVisibleItemIndex
if (selectedSectionIndex != currentSectionIndex) {
selectedSectionIndex = currentSectionIndex
scope.launch {
sectionsListState.animateScrollToItem(currentSectionIndex)
}
}
}
)
}
}
view raw MenuView.kt hosted with ❤ by GitHub

That’s it! You made it! 💪

Do you see anything missing? please comment!

Thanks for reading, see you in the next article 😊

This article was originally published on proandroiddev.com on July 13, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu