NavigationView custom menu item

from the CommonsWare Community archives

At 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:

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: