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)
) {}
}
}
}

Job Offers

Job Offers


    Delivery Lead / Scrum Master (m/w/d)

    Deutsche Post IT Services (Berlin) GmbH
    Berlin
    • Full Time
    apply now

    Talent Acquisition Manager – Technology

    FanDuel
    New York, NY; Atlanta, GA
    • Full Time
    apply now

    Android Developer

    ClearScore
    London (Hybrid role)
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

, ,

Painless, Typesafe Jetpack Compose Navigation with Voyager

Jetpack Compose Navigation by Google has so many drawbacks like no typesafety, specifying the whole NavGraph at startup and fuzzing around with ids. It could all be so simple: Why not just define screens by…
Watch Video

Painless, Typesafe Jetpack Compose Navigation with Voyager

Alexander Steenbergen
Android Dev Lead
IBM

Painless, Typesafe Jetpack Compose Navigation with Voyager

Alexander Steenber ...
Android Dev Lead
IBM

Painless, Typesafe Jetpack Compose Navigation with Voyager

Alexander Steenb ...
Android Dev Lead
IBM

Jobs

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

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
blog
Yes! You heard it right. We’ll try to understand the complete OTP (one time…
READ MORE

Leave a Reply

Your email address will not be published.

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

Menu