Search code examples
androidxmlbottomnavigationviewmaterial-componentsmaterial-components-android

Android - Change color of icon and title of each tab bottom navigation


I'm trying to make a bottom navigation a bit tricky. Indeed I want this kind of bottom navigation : bottom_nav_1 bottom_nav_2 Each tab has a different color when it is selected. For example measure will be in red when selected (icon and title) and profile will be green when selected...
So I tried to use a selector per item (in my menu)
But the color is not applied. The icon change successfully (I tried to put a completely different icon when an item is selected) but not the color of the title of the tab.
I tried to remove 2 properties from my bottom navigation :

app:itemTextColor="@color/black"
app:itemIconTint="@color/black"

But it's getting worse because the color of my theme app (primary) is applied when a tab is selected.

My bottom nav :

<com.google.android.material.bottomnavigation.BottomNavigationView
                android:id="@+id/bottom_navigation"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/white"
                app:menu="@menu/main_bottom_navigation"
                style="@style/BottomNavigationView"
                app:labelVisibilityMode="labeled"
                android:layout_alignParentBottom="true" />

One of my selector (logic applied to all items):

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Pressed state -->
    <item android:drawable="@drawable/bottom_bar_journal_on"
            android:color="@color/red_FF623E"
            android:state_checked="true"/>
    <!-- Default state -->
    <item android:drawable="@drawable/bottom_bar_journal_off"
            android:color="@color/background_yellow"
            android:state_checkable="false"/>

</selector>

And my menu (where I apply all of my selector) :

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
            android:id="@+id/action_journal"
            android:enabled="true"
            android:icon="@drawable/bottom_bar_journal"
            android:title="@string/main_menu_journal"
            app:showAsAction="withText" />

    <item
            android:id="@+id/action_measure"
            android:enabled="true"
            android:icon="@drawable/bottom_bar_measure_off"
            android:title="@string/main_menu_measure"
            app:showAsAction="withText" />

    <item
            android:id="@+id/action_add"
            android:enabled="false"
            android:title=""
            app:showAsAction="withText" />

    <item
            android:id="@+id/action_treatment"
            android:enabled="true"
            android:icon="@drawable/bottom_bar_treatment_off"
            android:title="@string/main_menu_treatment" />

    <item
            android:id="@+id/action_profile"
            android:enabled="true"
            android:icon="@drawable/bottom_bar_profile"
            android:title="@string/main_menu_profile"
            app:showAsAction="withText" />
</menu>

Solution

  • Bottom navigation bar overrides icon colors via app:itemIconTint, but removing that attribute from the XML just makes the nav bar use the app's default colors instead. To prevent the bar from applying color changes, and let your selectors work as intended, you have to set the icon tint list to null in code, like this:

    bottom_navigation_bar.itemIconTintList = null
    

    EDIT: I see you want to color the text as well, that's a bit trickier. Only solution I can come up with is to forget about drawable selectors, and just replace the item's icon tint list and text color every time the bottom nav bar selection changes. In your navbar-hosting activity, define these two:

    private val bottomNavBarStateList = arrayOf(
        intArrayOf(android.R.attr.state_checked),
        intArrayOf(-android.R.attr.state_checked)
    )
    
    private fun updateBottomNavBarColor(currentSelectedColor: Int) {
        val colorList = intArrayOf(
            ContextCompat.getColor(this, currentSelectedColor),
            ContextCompat.getColor(this, R.color.text_tertiary)
        )
        val colorStateList = ColorStateList(bottomNavBarStateList, colorList)
        bottom_navigation_bar.itemIconTintList = colorStateList
        bottom_navigation_bar.itemTextColor = colorStateList
    }
    

    Then in your navbar's ItemSelectedListener, call updateBottomNavBarColor with the color you want:

    bottom_navigation_bar.setOnNavigationItemSelectedListener {
        when(it.itemId) {
            R.id.bottom_nav_action_treatment -> {
                updateBottomNavBarColor(R.color.treatment)
            }
            R.id.bottom_nav_action_profile -> {
                updateBottomNavBarColor(R.color.profile)
            }
            else -> {
    
            }
        }
    }