Hide FloatingActionButton on scroll of RecyclerView

ScrollAndroid RecyclerviewFloating Action-ButtonAndroid Coordinatorlayout

Scroll Problem Overview


I want to hide/show FloatingActionButton on scroll of RecyclerView.

My XML layout :

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

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recyclerview_eventlist"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

            <android.support.design.widget.FloatingActionButton
                android:id="@+id/fab_createevent"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/fab_margin"
                app:layout_anchor="@id/recyclerview_eventlist"
                app:layout_anchorGravity="bottom|right|end"
                app:layout_behavior="com.eventizon.behavior.ScrollAwareFABBehavior"
                android:src="@drawable/ic_edit"
                app:backgroundTint="@color/custom_color_1"
                app:borderWidth="0dp" />
        </android.support.design.widget.CoordinatorLayout>

DrawerLayout is the parent layout of this layout.

    public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {


	private static final String TAG = "ScrollAwareFABBehavior";

	public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
		super();
		Log.e(TAG,"ScrollAwareFABBehavior");
	}

	@Override
	public void onNestedScroll(CoordinatorLayout coordinatorLayout,
			FloatingActionButton child, View target, int dxConsumed,
			int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
		// TODO Auto-generated method stub
		super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
				dxUnconsumed, dyUnconsumed);
		Log.e(TAG,"onNestedScroll called");
		if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
			Log.e(TAG,"child.hide()");
			child.hide();
		} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
			Log.e(TAG,"child.show()");
			child.show();
		}
	}
}

Used this layout behaviour for FloatingActionButton.

When I see logcat only constructor is getting called. onNestedScroll() doesn't get called when I scroll the list.

Scroll Solutions


Solution 1 - Scroll

Easiest solution:

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy)
    {
        if (dy > 0 ||dy<0 && fab.isShown())
        {
            fab.hide();
        }
    }

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState)
    {
        if (newState == RecyclerView.SCROLL_STATE_IDLE)
        {
            fab.show();
        }

        super.onScrollStateChanged(recyclerView, newState);
    }
});

Solution 2 - Scroll

This should work for you:

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
	@Override
	public void onScrolled(RecyclerView recyclerView, int dx,int dy){
		super.onScrolled(recyclerView, dx, dy);

		if (dy >0) {
			// Scroll Down
			if (fab.isShown()) {
				fab.hide();
			}
		}
		else if (dy <0) {
			// Scroll Up
			if (!fab.isShown()) {
				fab.show();
			}
		}
	 }
});

Solution 3 - Scroll

Ok, here is what you need:

First, since your FAB depends on the RecyclerView, add the following to your behavior class:

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
    if(dependency instanceof RecyclerView)
        return true;

    return false;
}

Next, in order to receive onNestedScroll() calls, you need to override this:

 public boolean onStartNestedScroll(CoordinatorLayout parent, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {

    //predict whether you will need to react to the RecyclerView's scroll;
    //if yes, return true, otherwise return false to avoid future calls
    //of onNestedScroll()
    return true;
}

Good luck!

Update

Here is what your ScrollAwareFABBehavior should look like:

public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
	private static final String TAG = "ScrollAwareFABBehavior";

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

	public boolean onStartNestedScroll(CoordinatorLayout parent, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
	    return true;
	}

	@Override
	public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
	    if(dependency instanceof RecyclerView)
	        return true;

	    return false;
	}

	@Override
	public void onNestedScroll(CoordinatorLayout coordinatorLayout,
	                           FloatingActionButton child, View target, int dxConsumed,
	                           int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
	    // TODO Auto-generated method stub
	    super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
	            dxUnconsumed, dyUnconsumed);
	   
	    if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {	  
	        child.hide();
	    } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
	        child.show();
	    }
	}
}

Also, it was tested using com.android.support:design:23.0.1

Solution 4 - Scroll

Short and Simple Solution:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (dy > 0 && mFloatingActionButton.getVisibility() == View.VISIBLE) {
            mFloatingActionButton.hide();
        } else if (dy < 0 && mFloatingActionButton.getVisibility() !=View.VISIBLE) {
            mFloatingActionButton.show();
        }
    }
});

Solution 5 - Scroll

If you are using Material Components for Android and your FAB is inside a CoordinatorLayout then you can use the layout_behavior com.google.android.material.behavior.HideBottomViewOnScrollBehavior

   <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/filter_fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ...
            app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior" 
            ... />

Solution 6 - Scroll

If you are not using coordinator layout and want to hide and show FAB smoothly. And you want to implement your own logic to hide fab on scroll down, and show it on scrolling up.

Then here is the solution in kotlin,

  • Declare a variable,
var scrollingDown = false
  • After that create a listener,
recycler_view_id.addOnScrollListener(object : RecyclerView.OnScrollListener() {

            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)

                if (scrollingDown && dy >= 0) {
                    scrollingDown = !scrollingDown
                    id_show_media_fab.startAnimation(
                        AnimationUtils.loadAnimation(
                            getApplicationContext(),
                            R.anim.fab_close
                        )
                    )
                } else if (!scrollingDown && dy < 0) {
                    scrollingDown = !scrollingDown
                    id_show_media_fab.startAnimation(
                        AnimationUtils.loadAnimation(
                            getApplicationContext(),
                            R.anim.fab_open
                        )
                    )
                }

            }
        })
  • create anim resource file

fab_open.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">
    <scale
        android:duration="300"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:interpolator="@android:anim/linear_interpolator"
        android:pivotX="50%"
        android:pivotY="100%"
        android:toXScale="0.9"
        android:toYScale="0.9" />
    <alpha
        android:duration="300"
        android:fromAlpha="0.0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:toAlpha="1.0" />
</set>

fab_close.xml

just change 

android:fromXScale="0.8"
android:fromYScale="0.8"

Solution 7 - Scroll

This is how I did. It works for me! If you don't know how to implement, You can see detail in this link https://guides.codepath.com/android/floating-action-buttons

public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {

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

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
            child.hide();
        } else if (dyConsumed < 0 && child.getVisibility() == View.GONE) {
            child.show();
        }
    }

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

Solution 8 - Scroll

I have created a custom RecyclerView which has OnUpDownScrollListener, OnLeftRightScrollListener ready:

###Code: MBRecyclerView.java

public class MBRecyclerView extends RecyclerView {

    private OnScrollListener wrappedUpDownScrollListener;
    private OnScrollListener wrappedLeftRightScrollListener;

    public MBRecyclerView(Context context) {
        super(context);
        init();
    }

    public MBRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MBRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
    }

    // region Scrolling Listener for Up, Down, Left and Right
    public void setOnUpDownScrollListener(final OnUpDownScrollListener onUpDownScrollListener) {
        if (wrappedUpDownScrollListener == null) {
            wrappedUpDownScrollListener = getWrappedUpDownScrollListener(onUpDownScrollListener);
            addOnScrollListener(wrappedUpDownScrollListener);
        }
    }

    public void removeOnUpDownScrollListener() {
        if (wrappedUpDownScrollListener != null) {
            removeOnScrollListener(wrappedUpDownScrollListener);
            wrappedUpDownScrollListener = null;
        }
    }

    public void setLeftOnRightScrollListener(final OnLeftRightScrollListener onLeftRightScrollListener) {
        if (wrappedLeftRightScrollListener == null) {
            wrappedLeftRightScrollListener = getWrappedLeftRightScrollListener(onLeftRightScrollListener);
            addOnScrollListener(wrappedLeftRightScrollListener);
        }
    }

    public void removeOnLeftRightScrollListener() {
        if (wrappedLeftRightScrollListener != null) {
            removeOnScrollListener(wrappedLeftRightScrollListener);
            wrappedLeftRightScrollListener = null;
        }
    }

    private OnScrollListener getWrappedUpDownScrollListener(final OnUpDownScrollListener onUpDownScrollListener) {
        return new OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                if (onUpDownScrollListener != null) {
                    // Negative to check scrolling up, positive to check scrolling down
                    if (!recyclerView.canScrollVertically(-1)) {
                        onUpDownScrollListener.onScrolledToTop();
                    } else if (!recyclerView.canScrollVertically(1)) {
                        onUpDownScrollListener.onScrolledToBottom();
                    }
                    if (dy > 0) {
                        onUpDownScrollListener.onScrollDown(dy);
                    } else if (dy < 0) {
                        onUpDownScrollListener.onScrollUp(dy);
                    }
                }
            }

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    if (onUpDownScrollListener != null) {
                        onUpDownScrollListener.onScrollStopped();
                    }
                }
            }
        };
    }

    private OnScrollListener getWrappedLeftRightScrollListener(final OnLeftRightScrollListener onLeftRightScrollListener) {
        return new OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (onLeftRightScrollListener != null) {
                    // Negative to check scrolling left, positive to check scrolling right
                    if (!recyclerView.canScrollHorizontally(-1)) {
                        onLeftRightScrollListener.onScrolledToMostLeft();
                    } else if (!recyclerView.canScrollVertically(1)) {
                        onLeftRightScrollListener.onScrolledToMostRight();
                    }
                    if (dy > 0) {
                        onLeftRightScrollListener.onScrollRight(dx);
                    } else if (dy < 0) {
                        onLeftRightScrollListener.onScrollLeft(dx);
                    }
                }
            }

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    if (onLeftRightScrollListener != null) {
                        onLeftRightScrollListener.onScrollStopped();
                    }
                }
            }
        };
    }

    public abstract class OnUpDownScrollListener {
        public void onScrollUp(int dy) {}

        public void onScrollDown(int dy) {}

        public void onScrolledToTop() {}

        public void onScrolledToBottom() {}

        public void onScrollStopped() {}
    }

    public abstract class OnLeftRightScrollListener {
        public void onScrollLeft(int dx) {}

        public void onScrollRight(int dx) {}

        public void onScrolledToMostRight() {}

        public void onScrolledToMostLeft() {}

        public void onScrollStopped() {}
    }
    // endregion
}

###Usage (UpDownScrollListener):

    mbRecyclerView.setOnUpDownScrollListener(new MBRecyclerView.OnUpDownScrollListener() {
        @Override
        public void onScrollUp(int dy) {
            // show
        }

        @Override
        public void onScrollDown(int dy) {
            // hide
        }

        // aditional functions:
        public void onScrolledToTop() {}
        public void onScrolledToBottom() {}
        public void onScrollStopped() {}
    });

similarely you can handle the LeftRight scrolling by setting

setOnLeftRightScrollListener

I hope it can help somebody :)

Solution 9 - Scroll

The solution is in: https://stackoverflow.com/questions/42414531/f-a-b-hides-but-doesnt-show/47994765#47994765

The problem is Android 25.0.x+ sets the view to GONE and thats why the listener is not reporting changes.

Solution 10 - Scroll

All the answers which goes on the Behavior path and onNestedScroll (instead of recyclerview listener) don't comment about the fact that onNestedScroll will be called many times while scrolling. This means that child.show() and child.hide() will be called many times as well. Although show() and hide() are designed to handle that scenario, they still run a lot of code and create some objects which multiplied by the times onNestedScroll is called, result in a lot of objects created unnecessarily.

Considering that and because I wanted to run a different animation instead of the default show() and hide(), I came up with the following Behavior implementation:

public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton>  {

private static final String TAG = "ScrollAwareFABBehavior";

private boolean fabAnimationStarted = false;
private boolean flingHappened = false;

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

@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {

    if (target instanceof RecyclerView) {
        return true;
    }
    return false;
}

@Override
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull final FloatingActionButton child, @NonNull View target, int type) {
    super.onStopNestedScroll(coordinatorLayout, child, target, type);

    // If animation didn't start, we don't need to care about running the restore animation.
    // i.e.: when the user swipes to another tab in a viewpager. The onNestedPreScroll is never called.
    if (!fabAnimationStarted) {
        return;
    }

    // Animate back when the fling ended (TYPE_NON_TOUCH)
    // or if the user made the touch up (TYPE_TOUCH) but the fling didn't happen.
    if (type == ViewCompat.TYPE_NON_TOUCH || (type == ViewCompat.TYPE_TOUCH && !flingHappened)) {
        ViewCompat.animate(child).translationY(0).start();
        
        fabAnimationStarted = false;
    }
}

@Override
public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View target, float velocityX, float velocityY, boolean consumed) {

    // We got a fling. Flag it.
    flingHappened = true;
    return false;

}

@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {

    if (!fabAnimationStarted) {
        Log.d(TAG, "onStartNestedScroll: animation is starting");
        fabAnimationStarted = true;
        flingHappened = false;
        CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();

        ViewCompat.animate(child).translationY(child.getHeight() + lp.bottomMargin).start();

    }
}
}

Solution 11 - Scroll

The floating action button will hide when there's scrolling and show when the scrolling stops.

  recylerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                    switch (newState) {
                        case RecyclerView.SCROLL_STATE_IDLE:
                            addExpenseBtn.show();
                            break;
                        default:
                            addExpenseBtn.hide();
                            break;
                    }
                    super.onScrollStateChanged(recyclerView, newState);
                }
            });

Solution 12 - Scroll

//lv = ListView

    lv.setOnScrollListener(new AbsListView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {

        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            fab.setVisibility(view.getFirstVisiblePosition() == 0 ? View.VISIBLE : View.INVISIBLE);
        }
    });

Solution 13 - Scroll

I used this in a RecyclerView.Adapter's onBindViewHolder method to set the bottom margin of the last item in the list to 72dp so that it will scroll up above the floating action button.

This does not require a dummy entry in the list.

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    // other binding code goes here.

    if (position + 1 == getItemCount()) {
        // set bottom margin to 72dp.
        setBottomMargin(holder.itemView, (int) (72 * Resources.getSystem().getDisplayMetrics().density));
    } else {
        // reset bottom margin back to zero. (your value may be different)
        setBottomMargin(holder.itemView, 0);
    }
}

public static void setBottomMargin(View view, int bottomMargin) {
    if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, bottomMargin);
        view.requestLayout();
    }
}

Solution 14 - Scroll

A little late, but it works for me.

//This only works on Android M and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
    recyclerView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
        @Override
        public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        if (scrollY > oldScrollY && fab.isShown()) {
            fab.hide();
        } else if (scrollY < oldScrollY && !fab.isShown()) {
            fab.show();
        }
    }
});

When scrollY is greater than oldScrollY, that means user has scroll down so, we just need to check FAB is showing. If it is, we hide it.

scrollY is less than oldScrollY means a scroll up. We check if FAB is still hidden to show it.

Solution 15 - Scroll

Try this

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                //Customize your if statement  
                if (recyclerView.computeVerticalScrollOffset() > recyclerView.getHeight() * 2) {
                    if (!fab.isShown()) {
                        fab.show();
                    }
                } else {
                    fab.hide();
                }
            }
        });

enjoy.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPritam KadamView Question on Stackoverflow
Solution 1 - ScrollDgotliebView Answer on Stackoverflow
Solution 2 - ScrollFaraAndroidView Answer on Stackoverflow
Solution 3 - ScrollAriView Answer on Stackoverflow
Solution 4 - ScrollYash ParikhView Answer on Stackoverflow
Solution 5 - ScrollArpan SarkarView Answer on Stackoverflow
Solution 6 - ScrollVikas AcharyaView Answer on Stackoverflow
Solution 7 - ScrollThetNaing MizoView Answer on Stackoverflow
Solution 8 - ScrollMBHView Answer on Stackoverflow
Solution 9 - ScrollIvorView Answer on Stackoverflow
Solution 10 - ScrollFrancisco JuniorView Answer on Stackoverflow
Solution 11 - ScrollRZ TutulView Answer on Stackoverflow
Solution 12 - ScrollFCF75View Answer on Stackoverflow
Solution 13 - ScrollNaveen Kumar MView Answer on Stackoverflow
Solution 14 - ScrollAntony HRView Answer on Stackoverflow
Solution 15 - ScrollAITAALI_ABDERRAHMANEView Answer on Stackoverflow