Search code examples
androidandroid-layoutandroid-coordinatorlayoutbottomnavigationview

Show/hide BottomNavigationView on scroll in CoordinatorLayout with AppBarLayout


I am trying to use both AppBarLayout and BottomNavigationLayout in a single CoordinatorLayout and I'm having difficulties hiding the BottomNavigationLayout as required by the material guideline.

I mean something like this:

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="false">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_insetEdge="top"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:layout_scrollFlags="scroll|enterAlways"/>
    </android.support.design.widget.AppBarLayout>


    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottom_nav"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:layout_gravity="bottom"
        app:menu="@menu/menu_bottom_navigation"/>

    <FrameLayout
        android:id="@+id/content_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</android.support.design.widget.CoordinatorLayout>

As you can see, I also have a FrameLayout that's used to contain a fragment with the actual content. Currently there are no default/built-in behaviors for the BottomNavigationView - neither for the view itself, nor for its siblings. The existing appbar_scrolling_view_behavior handles the content view in coordination with the appbar but ignores other siblings.

I am looking for a solution to hide and show both the appbar and the bottom navigation view on scroll.


Solution

  • After a day or two of searching I settled with a custom Behavior attached to the BottomNavigationView. Its main idea is to detect when the BottomNavigationView's sibling is scrolled so that it can hide the BottomNavigationView. Something like this:

    public class BottomNavigationBehavior extends CoordinatorLayout.Behavior<BottomNavigationView> {
    
        public BottomNavigationBehavior() {
            super();
        }
    
        public BottomNavigationBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, BottomNavigationView child, View dependency) {
            boolean dependsOn = dependency instanceof FrameLayout;
            return dependsOn;
        }
    
        @Override
        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, BottomNavigationView child, View directTargetChild, View target, int nestedScrollAxes) {
            return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
        }
    
        @Override
        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, BottomNavigationView child, View target, int dx, int dy, int[] consumed) {
            if(dy < 0) {
                showBottomNavigationView(child);
            }
            else if(dy > 0) {
                hideBottomNavigationView(child);
            }
        }
    
        private void hideBottomNavigationView(BottomNavigationView view) {
            view.animate().translationY(view.getHeight());
        }
    
        private void showBottomNavigationView(BottomNavigationView view) {
            view.animate().translationY(0);
        }
    }
    

    As you can see, I'm using simple ViewPropertyAnimator, obtained using the child views's animate method. This leads to a simple animation that doesn't really match the AppBarLayout's behavior but it's decent enough to look good and at the same time it's simple enough to implement.

    I expect that at some point the Android team will add a default Behavior for the BottomNavigationView in the support library so I don't think it's reasonable to invest a lot more time to exactly duplicate the AppBarLayout's behavior.

    edit (April 2018): see the comments section for a minor clarification about onStartNestedScroll and onNestedPreScroll and their new versions.