0

I'm developing an Android app that follows the single activity pattern. In one of my fragments I have a RecyclerView and in the case where the user scrolls down I want to hide my BottomNavigationView.

I have already seen other posts about this matter but none of them seems to help my with my issue. So far I've tried making the bottom nav view and my host fragment childs of CoordinatorLayout as well as adding the app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" property on my BottomNavigationView. I have also tried to implement this behaviour manually in code but that is not working either.

Here is my activity.xml which contains my BottomNavigationView and my host fragment.

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.MainActivity">

        <fragment
            android:id="@+id/fragmentMaster"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:defaultNavHost="true"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toTopOf="@id/bottomNav"
            app:layout_constraintTop_toTopOf="parent"
            app:navGraph="@navigation/nav_graph" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNav"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:menu="@menu/menu_bottom_nav">

        </com.google.android.material.bottomnavigation.BottomNavigationView>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

And this is the fragment with the RecyclerView

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <com.google.android.material.appbar.MaterialToolbar
                android:id="@+id/topAppBar"
                style="@style/Widget.MaterialComponents.Toolbar.Primary"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_scrollFlags="scroll"
                app:title="@string/app_name" />

            <HorizontalScrollView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#FFFFFF"
                android:scrollbars="none"
                app:layout_scrollFlags="scroll">

                <com.google.android.material.chip.ChipGroup
                    android:id="@+id/filterChips"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="8dp"
                    app:singleLine="true">

                    <com.google.android.material.chip.Chip
                        android:id="@+id/breakfastChip"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:checkable="true"
                        android:text="@string/breakfast" />

                    <com.google.android.material.chip.Chip
                        android:id="@+id/mealChip"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:checkable="true"
                        android:text="@string/meal" />

                    <com.google.android.material.chip.Chip
                        android:id="@+id/dinnerChip"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:checkable="true"
                        android:text="@string/dinner" />

                    <com.google.android.material.chip.Chip
                        android:id="@+id/veganChip"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:checkable="true"
                        android:text="@string/vegan" />

                    <com.google.android.material.chip.Chip
                        android:id="@+id/vegetarianChip"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:checkable="true"
                        android:text="@string/vegetarian" />

                    <com.google.android.material.chip.Chip
                        android:id="@+id/regularChip"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:checkable="true"
                        android:text="@string/regular" />

                </com.google.android.material.chip.ChipGroup>

            </HorizontalScrollView>

        </com.google.android.material.appbar.AppBarLayout>

        
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recipeList"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            tools:listitem="@layout/item_recipe_list">
        </androidx.recyclerview.widget.RecyclerView>


        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/newRecipeBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            android:contentDescription="@string/add_recipe"
            android:src="@drawable/ic_baseline_add_24">

        </com.google.android.material.floatingactionbutton.FloatingActionButton>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

Edit: The issue was that the inner CoordinatorLayout was consuming the scroll event and so the AppBarLayout worked as expected, but the outer CoordinatorLayout that manages the BottomNavView never received the scroll notification. The solution is to overwrite the CoordinatorLayout implementation so that it propagates to the parent CoordinatorLayout.

  • after adding the behavior now make the parent of you fragment in which you have your recycler view have a nested scroll view...that should solve the issue – unownsp May 03 '21 at 10:40
  • @unownsp You mean wrapping the CoordinatorLayout of my fragment with NestedScrollView? – tinerfinha May 03 '21 at 10:48

1 Answers1

0

Preview output

Create kotlin class NavigationViewBehavior.kt

import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.core.view.ViewCompat.NestedScrollType
import com.google.android.material.bottomnavigation.BottomNavigationView

class NavigationViewBehavior : CoordinatorLayout.Behavior<BottomNavigationView>() {
    private var height = 0
    override fun onLayoutChild(parent: CoordinatorLayout, child: BottomNavigationView, layoutDirection: Int): Boolean {
        height = child.height
        return super.onLayoutChild(parent, child, layoutDirection)
    }

    override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout,
                                     child: BottomNavigationView, directTargetChild: View, target: View,
                                     axes: Int, type: Int): Boolean {
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL
    }

    override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: BottomNavigationView,
                                target: View, dxConsumed: Int, dyConsumed: Int,
                                dxUnconsumed: Int, dyUnconsumed: Int,
                                @NestedScrollType type: Int) {
        if (dyConsumed > 0) {
            slideDown(child)
        } else if (dyConsumed < 0) {
            slideUp(child)
        }
    }

    private fun slideUp(child: BottomNavigationView) {
        child.clearAnimation()
        child.animate().translationY(0f).duration = 200
    }

    private fun slideDown(child: BottomNavigationView) {
        child.clearAnimation()
        child.animate().translationY(height.toFloat()).duration = 200
    }
}

activity_main.xml

    <?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/business_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:hardwareAccelerated="true">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/coordinator_business_layout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">


        <FrameLayout
            android:id="@+id/frame_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="?actionBarSize"
            android:orientation="vertical">


            <fragment
                android:id="@+id/nav_host_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:navGraph="@navigation/mobile_navigation"
                tools:ignore="FragmentTagUsage" />

        </FrameLayout>

       

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/nav_view"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:layout_gravity="bottom"
            android:background="@color/colorWhite"
            app:itemBackground="@drawable/border_bottom_ver"
            app:itemIconTint="@drawable/tab_color"
            app:itemTextColor="@drawable/tab_color"
            app:menu="@menu/bottom_nav_menu" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

call in MainActivity.kt

val navController = Navigation.findNavController(this,
                    R.id.nav_host_fragment)
            NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration!!)
            NavigationUI.setupWithNavController(navView!!, navController)
            val layoutParams = navView!!.getLayoutParams() as CoordinatorLayout.LayoutParams
            layoutParams.behavior = NavigationViewBehavior()
            val params = frameLayout.layoutParams as ViewGroup.MarginLayoutParams

            //hide bottom navigation no need while sun activities are being used
            navController.addOnDestinationChangedListener { controller: NavController?,
                                                            destination: NavDestination, arguments: Bundle? ->
                if (destination.id == R.id.navigation_fragments ) {
                    params.setMargins(0, 0, 0, 0)
                    frameLayout.layoutParams = params
                    navView!!.setVisibility(View.GONE)
                } else {
                    params.setMargins(0, 0, 0, 160)
                    frameLayout.layoutParams = params
                    navView!!.setVisibility(View.VISIBLE)
                }
            }

home_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvServices"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        android:visibility="visible" />

    <TextView
        android:id="@+id/no_values_assigned_message"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:text="@string/no_values_assigned"
        android:textSize="@dimen/fontSizeReg"
        android:textStyle="bold"
        android:visibility="gone" />

</androidx.constraintlayout.widget.ConstraintLayout>

this is the setting my RecyclerView

private fun RecyclerSetting(view: View) {
        no_value = view.findViewById(R.id.no_values_assigned_message)
        rvItem = view.findViewById(R.id.rvServices)
        rvItem!!.setHasFixedSize(true)

        //Very IMP This Line
        val manager = GridLayoutManager(activity, 2,
                GridLayoutManager.VERTICAL,
                false)
        data
        rvItem!!.setLayoutManager(manager)
    }

Visible

Hidden also specific fragment works

while you scroll recycler view auto-hide and also you can hide bottom navigation from specific Fragment code included

Ramesh
  • 504
  • 5
  • 9
  • I have changed my code to do the same as yours but it doesn't do anything, I don't really know why. The weird things is when I debug NavigationBehavior class, onLayoutChild is called but onNestedScroll never gets called. – tinerfinha May 04 '21 at 16:20
  • this code is working for me please check all code you have been changed any think is missed or not ***cross-verify your code*** – Ramesh May 04 '21 at 16:23
  • Also, you need to check where are you're missing – Ramesh May 04 '21 at 16:28
  • Could you show my the xml file which contains the RecyclerView? – tinerfinha May 04 '21 at 16:31
  • check I updated code @tinerfinha name `home_fragment.xml` and `RecyclerSetting` function – Ramesh May 04 '21 at 16:39
  • Your solution did not work for me, I finally fixed it and edited the main post, in case you want to know how I did it. Thanks, anyway :) – tinerfinha May 06 '21 at 10:23