Floating Action Button attached with anchor not hiding when scrolling RecyclerView items slow

NMP picture NMP · Aug 20, 2015 · Viewed 14.4k times · Source

I have the design provided at the bottom.

The things I want to happen above the tabs layout:

  1. I want to have an AppBarLayout which hides when the user scrolls down on the RecyclerView in one of the tabs.
  2. I want the user image to be attached to the blue background behind it and to hide when the user scrolls down.
  3. I want the tabs to be pinned at the top when the user scrolls so he can easily switch them. The recycler view and the tabs layout must be the only things visible to the user when he scrolls down.

So I did achieve 1 and 3, but I cannot achieve 2 - when you scroll slowly the RecyclerView the image stays over it and does not hide.

Here are the things I have done:

  1. I did implement the coordinator layout only in the fragment, which is displayed here. It is not implemented in a new activity or in the activity which hosts the fragment and the fragment does not have a toolbar. That means I am using a CollapsingToolbarLayout without a toolbar in it.
  2. I am using a floating action button from the support design library to display the user image. I use it because when it hides it does it with animation.

Check the XML at the bottom.

So what happens:

  1. Everything loads perfectly
  2. The button is attached at the correct position
  3. The AppBarLayout responds to scroll
  4. When scrolling slowly the button is not hiding
  5. When scrolling fast it does hide
  6. When changing viewpager fragments it does hide

Here is a video with the problem: YOUTUBE

I tried to debug the CoordinatorLayout and when it called the hide() method of the button but I couldn't find the problem. Any ideas?

Edit 1:

If you remove the toolbar in the cheesesquare example you will get the same bug.

My test xml:

<?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"
    android:clickable="true"
    android:background="@color/bg_secondary"
    android:fitsSystemWindows="true"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:elevation="1dp"
        android:fitsSystemWindows="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

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

            <ImageView
                android:id="@+id/img"
                android:src="@drawable/header_bg"
                android:scaleType="centerCrop"
                android:cropToPadding="true"
                app:layout_collapseMode="parallax"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

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


    <android.support.v4.view.ViewPager
        android:id="@+id/profile_tabs"
        android:elevation="2dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:elevation="1dp"
        app:pressedTranslationZ="12dp"
        android:clickable="true"
        app:rippleColor="@android:color/transparent"
        android:src="@drawable/ic_launcher"
        app:layout_collapseMode="parallax"
        app:layout_anchor="@id/app_bar_layout"
        app:layout_anchorGravity="bottom|left|end" />

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

Sample design

Edit 2:

There is also an issue marked as Declined for this problem.

Answer

dtx12 picture dtx12 · Sep 16, 2015

You have to implement custom behavior for FAB to achieve effect which you want. I slightly changed original implementation, to get it working without toolbar.

    public class FABBehavior extends FloatingActionButton.Behavior {

    public FABBehavior() {
    }

    public FABBehavior(Context context, AttributeSet attributeSet) {
    }

    public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
        if(dependency instanceof Snackbar.SnackbarLayout) {
           return super.onDependentViewChanged(parent, child, dependency);
        } else if(dependency instanceof AppBarLayout) {
            this.updateFabVisibility(parent, (AppBarLayout)dependency, child);
        }

        return false;
    }

    private boolean updateFabVisibility(CoordinatorLayout parent, AppBarLayout appBarLayout, FloatingActionButton child) {
        CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)child.getLayoutParams();
        if(lp.getAnchorId() != appBarLayout.getId()) {
            return false;
        } else {

            ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
            int point = child.getTop() - params.topMargin;
            try {
                Method method = AppBarLayout.class.getDeclaredMethod("getMinimumHeightForVisibleOverlappingContent");
                method.setAccessible(true);
                if(point <= (int) method.invoke(appBarLayout)) {
                    child.hide();
                } else {
                    child.show();
                }
                return true;
            } catch (Exception e) {
                return true;
            }
        }
    }
}

You may apply this behavior using app:layout_behavior of course.