Search code examples
androidbottomnavigationview

How to add indicator on a taller-than-default BottomNavigationView?


Background

I'm trying to have a bit larger BottomNavigationView (height is a bit bigger), while also making each item icon have a top indicator that it's selected.

Something like that, but with larger height:

enter image description here

It's a bit hard to see it on the screenshot, but the indicator is shown at the very top of the BottomNavigationView's item, right below the BottomNavigationView's shadow.

The problem

When I set a larger height, I get each item still take smaller height, so the indicator doesn't look at the top:

enter image description here

Not only that, but because I used itemBackground to set the background of each item to have the indicator, it now doesn't have the background used for the clicking effect.

What I've found

About the height, I've found this question on StackOverflow, of changing the height. The only solution there is to override the library's dimensions. In this case, it's only this:

<dimen name="design_bottom_navigation_height" tools:override="true">...</dimen>

However, this only solves the issue of putting the indicator at the top.

Plus, it looks like a dirty solution, to set the dimension forcefully for the library, and as I wrote, because I used itemBackground , it doesn't have the normal clicking effect anymore.

Here's the relevant code, modified just a bit from the wizard of Android Studio for making "Bottom Navigation Activity". The rest is the same as there :

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/container" 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:layout_width="match_parent"
    android:layout_height="match_parent" tools:context=".MainActivity">

    <TextView
        android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:layout_marginStart="@dimen/activity_horizontal_margin"
        android:layout_marginTop="@dimen/activity_vertical_margin" android:text="@string/title_home"
        app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent"/>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view" android:layout_width="0dp"
        android:layout_height="@dimen/tabs_height" android:layout_marginEnd="0dp"
        android:layout_marginStart="0dp" android:background="?android:attr/windowBackground" app:itemBackground="@drawable/activity_main__tab_background"
        app:labelVisibilityMode="unlabeled" app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu"/>

</androidx.constraintlayout.widget.ConstraintLayout>

dimens.xml

<dimen name="tabs_height">64dp</dimen>
<dimen name="design_bottom_navigation_height" tools:override="true">@dimen/tabs_height</dimen>

activity_main__tab_background.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true">
        <layer-list>
            <item android:gravity="top">
                <shape android:shape="rectangle">
                    <size android:height="2dp"/>
                    <solid android:color="#07a5ea"/>
                </shape>
            </item>
        </layer-list>
    </item>
</selector>

The question

How can I have the indicator at the top, while having a larger BottomNavigationView and without losing the clicking effect?


Solution

  • the best solution is to change the margin of the item imageView from kotlin code on runtime here is the code

    private fun navigationImagesMargin(view: View) {
            if (view is ViewGroup) {
                for (i in 0 until view.childCount) {
                    val child = view.getChildAt(i)
                    navigationImagesMargin(child)
                }
            } else if (view is ImageView) {
                val param = view.layoutParams as ViewGroup.MarginLayoutParams
                param.topMargin = convertDpToPx(14)
                view.layoutParams = param
            } 
        }
    fun convertDpToPx(dp: Int): Int {
        return Math.round(dp * (resources.displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT))
    }
    

    in the onCreate function call navigationImagesMargin function on the BottomNavigation View

    navigationImagesMargin(binding.spBottomNavigation)
    

    but this solution requires to recall navigationImagesMargin function everytime the view revalidated i.e. in the onItemClick listener here is the code for that

     binding.spBottomNavigation.setOnNavigationItemSelectedListener { it->
                binding.spBottomNavigation.post {
                    navigationImagesMargin(binding.spBottomNavigation)
                }
                true
            }
    

    in that way the images will stay where you aimed them to be on first launch

    this is a link for tiny repo that include the full code https://github.com/abdulmalekDery/BottomNavigationControl