Search code examples
androidandroid-layoutandroid-recyclerviewandroid-nestedscrollviewandroid-bottomappbar

Implementing app:hideOnScroll for BottomAppBar in Android


I have an app with the one Activity, many Fragments model, where several Fragments have a RecyclerView to show cards with content. I also have implemented the BottomAppBar from Material Design 2.0, and everything is fine except when the AppBar blocks the last CardView in the RecyclerView.

In terms of layout, I have a RecyclerView inside ConstraintLayout inside a Fragment, which sits in a FrameLayout in the main activity.

The documentation shows that for the BottomAppBar to be hidden on Scroll, we need to implement RecyclerView inside a NestedScrollView. There is one question here on SO where the answer has stated the same as well, but there seems to be no actual documentation or examples to demonstrate how this is to be done, except for this article on Medium, which uses the NestedScrollView in an Activity directly, holding a CoordinatorLayout which holds a ConstraintLayout.

Note: I think it also works on magic, because duplicating the layout in my fragment doesn't have any effect at all in my app.

How do I use NestedScrollView here?

PS : I need to have the TextView, as I set the RecyclerView to VISIBILITY.GONE and set the TextView to VISIBLE when I have no data to display.

Fragment Layout

<android.support.constraint.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/constraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="in.domain.APPNAME.Fragments.FragmentList">


    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerViewIncident"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:paddingBottom="30dp"
        android:visibility="visible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />

    <TextView
        android:id="@+id/emptyView"
        android:layout_width="wrap_content"
        android:layout_height="17dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="No Incidents to display"
        android:visibility="visible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.503"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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"
    android:id="@+id/uberLayout"
    tools:context=".APPNAME">

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@+id/containerFrameLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </FrameLayout>

    </android.support.constraint.ConstraintLayout>

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <android.support.design.bottomappbar.BottomAppBar
            android:id="@+id/bottom_app_bar"
            style="@style/Widget.MaterialComponents.BottomAppBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            app:backgroundTint="@color/colorPrimary"
            app:fabAlignmentMode="center"
            app:navigationIcon="@drawable/baseline_menu_white_24dp"
            app:hideOnScroll="true"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        </android.support.design.bottomappbar.BottomAppBar>

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/floatingActionButton"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:src="@drawable/baseline_add_white_24dp"
            app:backgroundTint="@color/brightred"
            app:fabSize="normal"
            app:layout_anchor="@+id/bottom_app_bar"
            tools:layout_editor_absoluteX="160dp"
            tools:layout_editor_absoluteY="465dp" />


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

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

Solution

  • you shouldn't put the BottomAppBar and the FloatingActionButton in a separate CoordinatorLayout. Ditch the CoordinatorLayout they're in, the ConstraintLayout around your FrameLayout and that may already solve the problem.

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout
        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"
        android:id="@+id/uberLayout"
        tools:context=".APPNAME">
    
        <FrameLayout
            android:id="@+id/containerFrameLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </FrameLayout>
    
        <android.support.design.bottomappbar.BottomAppBar
            android:id="@+id/bottom_app_bar"
            style="@style/Widget.MaterialComponents.BottomAppBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            app:backgroundTint="@color/colorPrimary"
            app:fabAlignmentMode="center"
            app:navigationIcon="@drawable/baseline_menu_white_24dp"
            app:hideOnScroll="true"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
    
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/floatingActionButton"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:src="@drawable/baseline_add_white_24dp"
            app:backgroundTint="@color/brightred"
            app:fabSize="normal"
            app:layout_anchor="@+id/bottom_app_bar" />
    
    </android.support.design.widget.CoordinatorLayout>
    

    I'm using a similar layout, the only difference being a <fragment> instead of a <FrameLayout>, and the BottomAppBar hides on scroll just fine. We don't need to use a NestedScrollView, if our scrolling content is a RecyclerView anyway, because RecyclerView implements NestedScrollingChild.

    This interface should be implemented by View subclasses that wish to support dispatching nested scrolling operations to a cooperating parent ViewGroup.