12

I have made an Android app with a recyclerview and a floating action button. When scrolling down, the button should hide, when scrolling up it should show again. I have used this tutorial to implement the behavior.

The result is, that the FAB hides when I scroll down, but when scrolling up it does not reappear again :( The class ScrollAwareFABBehavior is identical from the tutorial. But I'm using nested layouts.

Here is my layout (the recyclerview is in a LinearLayout in content_main):

<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:fitsSystemWindows="true"
    tools:context="org.myorganisation.mypackage.someActivity">

    <include layout="@layout/toolbar" />

    <include layout="@layout/content_main" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/add_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/plus"
        app:layout_behavior="org.myorganisation.mypackage.someActivity.helpers.ScrollAwareFABBehavior" />

    <LinearLayout
        android:id="@+id/insert_alert"
        android:layout_width="wrap_content"
        android:layout_height="50sp"
        android:layout_margin="@dimen/fab_margin"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:paddingEnd="70sp"
        android:visibility="gone"
        app:layout_anchor="@id/add_fab"
        app:layout_anchorGravity="bottom|left">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:text="@string/initial_alert"
            android:textColor="@color/colorPrimaryDark"
            android:textStyle="bold|italic" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_keyboard_arrow_right_black_24sp"
            android:tint="@color/colorPrimary" />
    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>
Yonjuni
  • 178
  • 2
  • 12

4 Answers4

5

As of version 25.1.0 of the Support Library, hidden views no longer scroll events as per this bug.

As mentioned in comment #5:

This is working as intended, your dependency chain is the wrong way. Your RecyclerView (Behavior) should be triggering the FAB visibility change.

ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • 1
    You can also switch to the approach recommended by the bug - there's an example [here](https://github.com/chrisbanes/cheesesquare/compare/master...ianhanniballake:scroll_aware_fab) – ianhanniballake Jan 13 '17 at 21:48
  • @ianhanniballake the new solution has a problem, it does not hide the fab if the list fills the viewport. Although this probably is desired because the fab may overlay the last item in the list. Any ideas how to solve that? I want to avoid adding a bottom padding to my list just to be able to scroll the content above the fab in this case... – prom85 May 23 '17 at 08:11
  • No need to change your dependency chain - the solution is here: https://stackoverflow.com/a/42082313/1112963 – Zon Jul 05 '17 at 05:07
  • 1
    Just because you found a workaround to keep doing it wrong does not make it right – ianhanniballake Jul 05 '17 at 05:11
  • @ianhanniballake True words! But I needed a quick fix first to get the time to make it right ;-) – Yonjuni Jan 26 '18 at 15:53
3

As for support library version 25.1.0, the solution is here.

Community
  • 1
  • 1
woxingxiao
  • 963
  • 9
  • 13
3

Here is simple solution:

The concept behind this is very simple, you just need to detect when user scrolls down and up the RecyclerView. In Android there are some built in methods that can help us detect when user is scrolling either side in Recyclerview. See the code below, since it will show the full concept:

RecyclerView rv = (RecyclerView)findViewById(R.id.rv);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

       rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
          @Override
          public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
          }

          @Override
          public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

              if (dy < 0) {
                  fab.show();

               } else if (dy > 0) {
                  fab.hide();
               }
         }
       });
halfer
  • 19,824
  • 17
  • 99
  • 186
Revansiddappa
  • 708
  • 1
  • 7
  • 16
2

I solved this problem by replacing .setVisibility(View.VISIBLE) with .show(true) and .setVisibility(View.GONE) with .hide(true):

And here is my class for the Behaviour, that works with 25.1.0: and Clans FloatingActionButton

public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
private static boolean mIsAnimatingOut = false;
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();

public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
    super();
}

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fabButton, View dependency) {
    return dependency instanceof Snackbar.SnackbarLayout;
}

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fabButton, View dependency) {
    float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
    fabButton.setTranslationY(translationY);
    return true;
}

@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
                                   FloatingActionButton fabButton, View directTargetChild, View target, int nestedScrollAxes) {
    return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
            super.onStartNestedScroll(coordinatorLayout, fabButton, directTargetChild, target,
                    nestedScrollAxes);

}

@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton fabButton,
                           View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
    super.onNestedScroll(coordinatorLayout, fabButton, target, dxConsumed, dyConsumed, dxUnconsumed,
            dyUnconsumed);

    if (fabButton.isEnabled()) {
        if (dyConsumed > 0 && !mIsAnimatingOut && fabButton.isShown()) {
            animateOut(fabButton);
        } else if ((dyConsumed < 0 || dyUnconsumed < 0) && fabButton.isHidden()) {
            animateIn(fabButton);
        }
    }
}

public static void animateOut(final FloatingActionButton fabButton) {
    if (Build.VERSION.SDK_INT >= 14) {
        ViewCompat.animate(fabButton).translationY(168F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer()
                .setListener(new ViewPropertyAnimatorListener() {
                    public void onAnimationStart(View view) {
                        mIsAnimatingOut = true;
                    }

                    public void onAnimationCancel(View view) {
                        mIsAnimatingOut = false;
                    }

                    public void onAnimationEnd(View view) {
                        mIsAnimatingOut = false;
                        fabButton.hide(true);
                    }
                }).start();
    } else {
        Animation anim = AnimationUtils.loadAnimation(fabButton.getContext(), android.R.anim.fade_in);
        anim.setInterpolator(INTERPOLATOR);
        anim.setDuration(200L);
        anim.setAnimationListener(new Animation.AnimationListener() {
            public void onAnimationStart(Animation animation) {
                mIsAnimatingOut = true;
            }

            public void onAnimationEnd(Animation animation) {
                mIsAnimatingOut = false;
                fabButton.hide(true);
            }

            @Override
            public void onAnimationRepeat(final Animation animation) {
            }
        });
        fabButton.startAnimation(anim);
    }
}

public static void animateIn(FloatingActionButton fabButton) {
    fabButton.show(true);
    if (Build.VERSION.SDK_INT >= 14) {
        ViewCompat.animate(fabButton).translationY(0).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
                .setInterpolator(INTERPOLATOR).withLayer().setListener(null)
                .start();
    } else {
        Animation anim = AnimationUtils.loadAnimation(fabButton.getContext(), android.R.anim.fade_out);
        anim.setDuration(200L);
        anim.setInterpolator(INTERPOLATOR);
        fabButton.startAnimation(anim);
    }
}

}

Scrounger
  • 261
  • 3
  • 5