Search code examples
androidandroid-layoutandroid-navigationview

Android: NavigationView selector for itemBackground


I have a NavigationView, with many items in it.

I'm trying to change the background of the selected (checked) item. I have a selector that changes the itemTextColor and that works fine.

I have a drawable resource:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="@color/selected_item_accent"/>
</shape>

And my selector:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/nav_view_background" android:state_checked="true" />
    <item android:drawable="@drawable/nav_view_background" android:state_pressed="true" />
    <item android:drawable="@android:color/transparent" />
</selector>

And lastly the NavigationView

<android.support.design.widget.NavigationView
    android:id="@+id/navView"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:background="@color/window_background"
    app:itemTextColor="@drawable/selector_nav_view_text"
    app:itemBackground="@drawable/selector_nav_view_background"
    app:headerLayout="@layout/app_nav_header" />

The pressed state works as expected, but the selected item background does not change.

I'm wondering what I'm doing wrong here. Thanks.


Solution

  • Search result

    Change the color of a checked menu item in a navigation drawer

    BUT i dont think it will work, because in the source code, it never set the real item of RecyclerView (not the item of menu) checked. That means when you click the item, only the item of menu checked. So of course the background will not changed.

    Workaround

    It seems that NavigationView is not designed for what you want. The designer may think that, when user clicks item in NavigationView, it should be closed and the app will navigate user to another page. So setChecked() is not needed;

    BUT there is a workaround using reflection. the core code is like

    navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                case R.id.navigation_menu_item_1:
                    // find out the position of the menu item in Adapter
                    // in my test demo, positionInAdapter = positionInMenu + 2
                    int positionInAdapter = NUMBER;
                    try {
                        Field presenterField = mNavigationSpan.getClass().getDeclaredField("mPresenter");
                        presenterField.setAccessible(true);
                        // get NavigationMenuPresenter from your NavigationView
                        NavigationMenuPresenter presenter = (NavigationMenuPresenter) presenterField.get(mNavigationSpan);
                        Field menuViewField = presenter.getClass().getDeclaredField("mMenuView");
                        menuViewField.setAccessible(true);
                        // get NavigationMenuView from NavigationMenuPresenter, it is a RecyclerView which contains the real items user seen
                        NavigationMenuView menuView = (NavigationMenuView) menuViewField.get(presenter);
                        // using RecyclerView.findViewHolderForAdapterPosition() to get the ViewHolder, then you can get the itemView
                        menuView.findViewHolderForAdapterPosition(positionInAdapter).itemView.setSelected(true);
                    } catch (NoSuchFieldException | IllegalAccessException e) {
                        e.printStackTrace();
                    }
                    break;
            }
            return false;
        }
    });
    

    Note

    1. as the ViewHolder.itemView is a View which doesnt have the setChecked method, so we use selected instead of checked;So you need to changed your background drawable xml, just change android:state_checked to android:state_selected
    2. using reflection is NOT a good idea, i will recommend you to write your own layout to feed your need instead of using NavigationView.