Anchor FloatingActionButton (Button) To RecyclerView and Hide/Show On Scroll (Kotlin)

April 21, 2018

If you are using Android Studio -> File -> New -> Activity -> Scrolling Activity with FloatingActionButton anchored to AppBarLayout with CollapsingToolbarLayout, FloatingActionButton is capable of automatic show and hide on scroll.

Sadly, by anchoring FloatingActionButton to NestedScrollView or RecyclerView (or shown without anchor), automatic show and hide FloatingActionButton on scroll doesn’t work.

<?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:id="@+id/rootView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="false"
    tools:context="com.mydomain.myapp.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay"
        app:expanded="true">

        <!-- app:layout_scrollFlags="scroll|exitUntilCollapsed" -->
        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbarLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:toolbarId="@+id/toolbar">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay" />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >

    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/fab_margin"
        app:layout_anchor="@id/appBar"
        app:layout_anchorGravity="bottom|end"
        android:tint="@color/fab_tint"
        app:srcCompat="@drawable/ic_edit_black_24dp" />

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

To enable FloatingActionButton to automatic show and hide on scroll, we need to create ScrollAwareFABBehavior by extending FloatingActionButton.Behavior.The following code is based on CodePath’s code and code suggestion by jairobjunior.

class ScrollAwareFABBehavior(context: Context, attrs: AttributeSet): FloatingActionButton.Behavior(context, attrs) {

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

    override fun onNestedScroll(coordinatorLayout: CoordinatorLayout,
                                child: FloatingActionButton, target: View, dxConsumed: Int, dyConsumed: Int,
                                dxUnconsumed: Int, dyUnconsumed: Int, type: Int) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
                dyUnconsumed, type)

        if (dyConsumed > 0 && child.visibility == View.VISIBLE) {
            child.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
                override fun onHidden(fab: FloatingActionButton) {
                    super.onHidden(fab)
                    fab.visibility = View.INVISIBLE
                }
            })
        // } else if (dyUnconsumed < 0 && child.visibility != View.VISIBLE) {
        } else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
            child.show()
        }
    }
}

Set FloatingActionButton app:layout_behavior to ScrollAwareFABBehavior for fab to auto hide and show on scroll.

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

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        app:layout_behavior="com.mydomain.myapp.ScrollAwareFABBehavior"
        android:layout_margin="@dimen/fab_margin"
        android:tint="@color/fab_tint"
        app:srcCompat="@drawable/ic_add_black_24dp" />

</android.support.design.widget.CoordinatorLayout>        
This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.