NavigationView custom menu item
from the CommonsWare Community archivesAt June 13, 2019, 11:32am, Raptor asked:
Hello,
I am trying to create a Drawer with a header and some menu items in it. I have created the header as a separate layout, and put it into the NavigationView like this:
<com.google.android.material.navigation.NavigationView
android:id="@+id/navView"
android:layout_width="312dp"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/menu_drawer">
The header appears correctly. The problem is that the menu is not customizable, by default. Therefore, I put the app:actionLayout="@layout/nav_menu_item"
attribute for each item in the menu, like this:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group>
<item android:id="@+id/navDocumentScanItem"
app:actionLayout="@layout/nav_menu_item"
android:title="@null"/>
<item android:id="@+id/navReportItem"
app:actionLayout="@layout/nav_menu_item"
android:title="@null"/>
<item android:id="@+id/navHallOfFameItem"
app:actionLayout="@layout/nav_menu_item"
android:title="@null"/>
</group>
In my nav_menu_item I have the layout that I want to use for each item in my drawer menu:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navItem"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="56dp">
<ImageView
android:id="@+id/navItemIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="6dp"
android:layout_marginStart="8dp"
android:layout_width="44dp"
android:layout_height="44dp"
tools:src="@drawable/nav_icon_qr_scan"/>
<TextView
android:id="@+id/navItemText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/navItemIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/menu_item_text_style"
tools:text="Document Scanning"/>
</androidx.constraintlayout.widget.ConstraintLayout>
This appears reasonably well (if I hardcode it), but the problem is that I can’t access the views in this custom layout. How do I get a handle of the “navItemIcon” and “navItemText” so that I can set the icon and text programatically, depending on which item position is for that particular item?
Thanks!
At June 13, 2019, 11:50am, mmurphy replied:
You could try:
- Calling
getMenu()
on theNavigationView
to get your inflatedMenu
resource - Calling
findItem()
on theMenu
to get theMenuItem
associated with a given item ID (e.g.,R.id.navDocumentScanItem
) - Calling
getActionView()
on theMenuItem
to get the inflatednav_menu_item
view hierarchy
I have not tried this with NavigationView
, but if I were in your shoes, it is what I would try.
At June 13, 2019, 11:52am, Raptor replied:
I have experimented with these in onCreate() but they return null, since they haven’t been created yet. What would be the right way to get them when they’re already created? In the past I’ve been using ViewTreeObserver or something like that - is that the correct way or is there another one?
At June 13, 2019, 12:07pm, mmurphy replied:
What specifically are you referring to as “they”? IOW, where is the null
showing up?
Beats me. I would have expected them to be ready after the view has been inflated.
Right now, your guess is as good as (or better than) mine.
At June 13, 2019, 2:28pm, Raptor replied:
I fixed it with ViewTreeObserver:
//handle the navigation drawer item
val navViewTreeObserver = navView.viewTreeObserver
navViewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
navView.viewTreeObserver.removeOnGlobalLayoutListener(this)
val menu = navView.menu
//get the menu items of the navigation view
for (i in 0 until menu.size()){
val menuItem = menu.getItem(i)
//get the relevant views that we want to change programmatically
val menuItemText = menuItem.actionView.findViewById<TextView>(R.id.navItemText)
val menuItemIcon = menuItem.actionView.findViewById<ImageView>(R.id.navItemIcon)
//change the menu item's text and icon depending on the position in the drawer
when (i){
0 -> {
menuItemText.text = "QR Scan"
menuItemIcon.setImageResource(R.drawable.nav_icon_qr_scan)
}
1 -> {
menuItemText.text = "Activity Reports"
menuItemIcon.setImageResource(R.drawable.nav_icon_report_inactive)
}
2 -> {
menuItemText.text = "Hall of fame"
menuItemIcon.setImageResource(R.drawable.nav_icon_hall_of_fame_inactive)
}
}
}
}
})
At June 13, 2019, 2:51pm, mmurphy replied:
Glad you got it working! Here are some ways you might simplify that:
- Use
findItem()
on theMenu
rather than iteration - Use
doOnNextLayout()
instead of rolling a fullViewTreeObserver
(this is in one of the-ktx
libraries)