59

Im using the FloatingActionButton from the android.support.design.widget package:

<android.support.design.widget.FloatingActionButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentEnd="true"
    android:layout_marginBottom="20dp"
    android:layout_marginEnd="16dp"
    android:clickable="true"
    android:backgroundTint="@color/primaryColor"
    android:src="@drawable/ic_search_white_24dp"
    app:borderWidth="0dp"
    app:elevation="6dp"
    app:backgroundTint="@color/primaryColorDark"
    app:rippleColor="@color/accentColor" />

Is it possible to configure that button to hide with an animation when the listview is scrolling down and to show it again when listview is scrolling up to the top?

Vadim Kotov
  • 7,766
  • 8
  • 46
  • 61
Mulgard
  • 8,785
  • 29
  • 120
  • 215
  • 1
    Use `CoordinatorLayout` from design lib – N J Jul 24 '15 at 18:53
  • I think you're talking about the coordinator layout in the support library. If that doesn't help you can hook it up manually and do the math: http://stackoverflow.com/questions/10713312/can-i-have-onscrolllistener-for-a-scrollview – oorosco Jul 24 '15 at 19:02
  • if you are using ListView and kotlin, you can use this [method](https://stackoverflow.com/a/46818665/8313609). – BlackHatVisions Oct 18 '17 at 20:30
  • if you are using ListViews and Kotlin, use this [method](https://stackoverflow.com/a/46818665/8313609). – BlackHatVisions Oct 18 '17 at 20:31

15 Answers15

75

Those who are looking to make it with recyclerview can do this:

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);
    }
});
VelocityPulse
  • 567
  • 6
  • 13
Irfan Raza
  • 2,752
  • 1
  • 20
  • 29
68

Sorry! I am late by years to answer this. I hope this still helps someone. This is also my first answer.

Mates! No need to implement scroll listeners.

Add the following to the floating action button xml:

app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"

giving:

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
        android:id="@+id/fabAddOItransferIn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:text="@string/btn_text_transfer_in"
        app:icon="@android:drawable/ic_input_add"
        app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

In response to the following comment of mine, "Sorry! I just noticed this has a weird side effect. Any snackbars will overlap this floating action button if app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior is added. ☹️ Taking this line off will prevent the overlap and the floating action button will behave as it is intended to inside the coordinator layout. "

To counter this, do use the following:

Snackbar.make(floating_action_button, "Some snackbar text!", BaseTransientBottomBar.LENGTH_SHORT).setAnchorView(floating_action_button).show();
  • 3
    This should be the recommended approach now. Thanks a lot! – Henrique Mar 12 '20 at 07:10
  • 5
    Do not forget to wrap the button inside CoordinatorLayout. – Benny Mar 16 '20 at 13:08
  • 1
    Sorry! I just noticed this has a weird side effect. Any snackbars will overlap this floating action button if app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior is added. ☹️ Taking this line off will prevent the overlap and the floating action button will behave as it is intended to inside the coordinator layout. – Jagadish Nallappa Apr 06 '20 at 07:43
  • 1
    If you're using [FloatingActionButtonSpeedDial](https://github.com/leinardi/FloatingActionButtonSpeedDial), you can add `speeddial_scrolling_view_snackbar_behavior` instead to make the FAB play nice with snackbars. – Big McLargeHuge May 20 '20 at 20:55
29

A small improvement to the code from Irfan Raza:

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

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

The Floating Action Button hides when scrolling down and shows when scrolling up.

Seven
  • 3,124
  • 1
  • 15
  • 14
  • This makes the FAB visible if you scroll in one direction, and it hides the FAB if you scroll in the opposite direction, totally different from the one posted by @Irfan Raza – akubi Sep 24 '19 at 07:33
26

See this. Here it tells how to do what you are trying to achieve. You have to use it like this in a CoordinatorLayout and ListView :

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/main_content"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

          <ListView
              android:id="@+id/lvToDoList"
              android:layout_width="match_parent"
              android:layout_height="match_parent"></ListView>

          <android.support.design.widget.FloatingActionButton
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_gravity="bottom|right"
              android:layout_margin="16dp"
              android:src="@drawable/ic_done"
              app:layout_anchor="@id/lvToDoList"
              app:layout_anchorGravity="bottom|right|end" />

</android.support.design.widget.CoordinatorLayout>
Clyde
  • 7,078
  • 3
  • 30
  • 52
Syed Ali Naqi
  • 671
  • 7
  • 15
  • The post you linked says `There is no support built-in for CoordinatorLayout to work with ListView according to this Google post.`. However you use `ListView` in your answer. I haven't achieved to make it work with `ListView`.. – Aritz Jan 18 '17 at 17:08
  • If this works it works only on API21+ when nested scrolling was introduced. – Eugen Pechanec Apr 01 '17 at 11:52
  • 6
    Why is this the accepted answer? It seems like CoordinatorLayout only works with RecyclerView. Am I missing something? – ejtt Jun 08 '17 at 05:16
  • @ejang Coordinator layout is best as it provides many useful flags if one wishes to use other functions like GooglePlayStore's Animation.. – Syed Ali Naqi Jul 15 '17 at 05:43
  • 2
    Can someone explain how this answer achieves the scroll behaviour @SyedAliNaqi asked about? Using the above XML doesn't add scroll behaviour. – A Droid Feb 09 '19 at 16:34
9

using this class you can easily animate you FAB, here I have implemented onStopNestedScroll() method to show your Fab whenever scroll stop. I set 1000 miliSeconds as delay using Handler();

public class FabBehaviour extends CoordinatorLayout.Behavior<FloatingActionButton> {
    private static final String TAG = "ScrollingFABBehavior";
    Handler mHandler;

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

    public FabBehaviour() {
        super();
    }

    @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull final FloatingActionButton child, @NonNull View target, int type) {
        super.onStopNestedScroll(coordinatorLayout, child, target, type);
        if (mHandler == null)
            mHandler = new Handler();


        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                child.animate().translationY(0).setInterpolator(new LinearInterpolator()).start();
                Log.d("FabAnim", "startHandler()");
            }
        }, 1000);
    }

    @Override
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
        if (dyConsumed > 0) {
            Log.d("Scrolling", "Up");
            CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            int fab_bottomMargin = layoutParams.bottomMargin;
            child.animate().translationY(child.getHeight() + fab_bottomMargin).setInterpolator(new LinearInterpolator()).start();
        } else if (dyConsumed < 0) {
            Log.d("Scrolling", "down");
            child.animate().translationY(0).setInterpolator(new LinearInterpolator()).start();
        }
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        if (mHandler != null) {
            mHandler.removeMessages(0);
            Log.d("Scrolling", "stopHandler()");
        }
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }


}

your_layout.xml

<android.support.design.widget.FloatingActionButton
        android:id="@+id/imageViewYes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end|right"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/ic_yes"
        app:backgroundTint="@color/white"
        android:scaleType="center"
        app:elevation="6dp"
        app:fabSize="normal"
        app:layout_behavior="com.your.package.FabBehaviour"
        app:pressedTranslationZ="12dp"
        app:rippleColor="@color/gray" />
Abdul Rizwan
  • 3,664
  • 29
  • 31
8

hey there is o require to take the recyclerview for auto hiding the floating action button on scrolling down for this purpose we can use default listview with floating action button in normal way only make modifications on listview.onscroll listener then we can get feel like recycle

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


        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            int lastItem = firstVisibleItem + visibleItemCount;
            if (lastItem == totalItemCount) {

                fab.setVisibility(View.INVISIBLE);
            }else {
                fab.setVisibility(View.VISIBLE);
            }
        }
    });
muni
  • 81
  • 1
  • 2
  • 2
    With this implementation in case the listview is not long enough the floating button will be set to `INVISIBLE` all time! – Robb1 Nov 08 '16 at 13:50
  • Just change `lastItem == totalItemCount` to `lastItem == totalItemCount && firstVisibleItem > 0` inside the if statement :D – Seven Aug 23 '17 at 20:09
4

There is my code in kotlin.

class ScrollAwareFABBehavior (val recyclerView: RecyclerView, val floatingActionButton: FloatingActionButton) {

    fun start() {
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)

                if (dy > 0) {
                    if (floatingActionButton!!.isShown) {
                        floatingActionButton?.hide()
                    }
                } else if (dy < 0) {
                    if (!floatingActionButton!!.isShown) {
                        floatingActionButton?.show()
                    }
                }
            }
        })
    }
}

Now, you just need to call the ScrollAwareFABBehavior with the recyclerView and the fab on constructor, then call method start().

ScrollAwareFABBehavior(recyclerView = recyclerViewPlaceFormContainer, floatingActionButton = floatingActionButton).start()
Elias Meireles
  • 706
  • 4
  • 9
4

Kotlin + DataBinding Adapter

@BindingAdapter("bindAdapter:attachFloatingButton")
fun bindRecyclerViewWithFB(recyclerView: RecyclerView, fb: FloatingActionButton) {
    recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)

            if (dy > 0 && fb.isShown) {
                fb.hide()
            } else if (dy < 0 && !fb.isShown) {
                fb.show()
            }
        }
    })
}

and the xml

    <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/main_recyclerview"
            android:layout_width="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            android:layout_height="wrap_content"
            android:clipToPadding="false"
            android:paddingBottom="8dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp"
            app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="8dp"
            android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@+id/main_chips"
            android:layout_marginBottom="8dp"
            **bindAdapter:attachFloatingButton="@{mainFb}"**
            app:layout_constraintBottom_toBottomOf="parent" 
            app:layout_constraintVertical_bias="0.0"/>


    <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
            android:id="@+id/main_fb"
            android:layout_width="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            style="@style/Widget.Design.FloatingActionButton"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_height="wrap_content"
            android:layout_margin="18dp"
            android:background="@color/colorPrimaryDark"
            app:icon="@drawable/ic_add_black_24dp"/>
Jesus Dimrix
  • 4,294
  • 4
  • 26
  • 62
3

Here I am adding extra padding for last view item to avoid overlapping list item with floating action button

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();
    }
}
Naveen Kumar M
  • 7,200
  • 6
  • 59
  • 68
2

According to me the best way to implement this would be as below.

public class ScrollingFABBehavior extends FloatingActionButton.Behavior {


private static final String TAG = "ScrollingFABBehavior";

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


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);
    //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();
    }
}}

For detailed answer check this out. Hide FloatingActionButton on scroll of RecyclerView

Community
  • 1
  • 1
PravinDodia
  • 3,201
  • 1
  • 16
  • 22
2

for Kotlin it is very simple (API 23+)

myRecyclerView.setOnScrollChangeListener { _, _, _, _, oldScrollY ->
    if (oldScrollY < 0) myFAB.hide() else myFAB.show()
}
2

Another method for recycleView using kotlin extensions.

fun RecyclerView.attachFab(fab : FloatingActionButton) {
    this.addOnScrollListener(object : RecyclerView.OnScrollListener(){
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            if (dy > 0)
                fab.hide()
            else if (dy < 0)
                fab.show()
        }
    })
}

Now you can attach fab to any recycleView with:

rv.attachFab(requireActivity().fab)
// in my case i made fab public on activity
Nithin
  • 4,672
  • 32
  • 43
1
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();
    }
}});
Suraj Vaishnav
  • 6,867
  • 3
  • 40
  • 43
0

Just to add, for NestedScrollView the approach will be something like the following:

        // register the extended floating action Button
        final ExtendedFloatingActionButton extendedFloatingActionButton = findViewById(R.id.extFloatingActionButton);
  
        // register the nestedScrollView from the main layout
        NestedScrollView nestedScrollView = findViewById(R.id.nestedScrollView);
  
        // handle the nestedScrollView behaviour with OnScrollChangeListener
        // to extend or shrink the Extended Floating Action Button
        nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
            @Override
            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                // the delay of the extension of the FAB is set for 12 items
                if (scrollY > oldScrollY + 12 && extendedFloatingActionButton.isExtended()) {
                    extendedFloatingActionButton.shrink();
                }
  
                // the delay of the extension of the FAB is set for 12 items
                if (scrollY < oldScrollY - 12 && !extendedFloatingActionButton.isExtended()) {
                    extendedFloatingActionButton.extend();
                }
  
                // if the nestedScrollView is at the first item of the list then the
                // extended floating action should be in extended state
                if (scrollY == 0) {
                    extendedFloatingActionButton.extend();
                }
            }
        });

I've taken this code from GeeksForGeeks

ganjaam
  • 675
  • 1
  • 11
  • 25
0

You can use HideBottomViewOnScrollBehavior on your ExtendedFloatingActionButton.

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
            android:id="@+id/main_fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/text_analyze"
            android:layout_margin="@dimen/theme_space"
            app:icon="@drawable/ic_baseline_refresh_24"
            app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
            android:layout_gravity="bottom|end" />

You can also use Gmail style collapse/expand behavior instead of hiding the FAB.

Create CollapseFABOnScrollBehavior.java

public class CollapseFABOnScrollBehavior <V extends View> extends CoordinatorLayout.Behavior<V> {
    private final int offsetDp = 8; // You can increase or decrease this value. This offset is necessary for a better user experience.
    private static final int STATE_SCROLLED_DOWN = 1;
    private static final int STATE_SCROLLED_UP = 2;
    private int currentState = STATE_SCROLLED_UP;

    public CollapseFABOnScrollBehavior() {
    }

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

    @Override
    public boolean onStartNestedScroll(
            @NonNull CoordinatorLayout coordinatorLayout,
            @NonNull V child,
            @NonNull View directTargetChild,
            @NonNull View target,
            int axes,
            int type) {
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    @Override
    public void onNestedScroll(
            @NonNull CoordinatorLayout coordinatorLayout,
            @NonNull V child,
            @NonNull View target,
            int dxConsumed,
            int dyConsumed,
            int dxUnconsumed,
            int dyUnconsumed,
            int type,
            @NonNull int[] consumed) {
        int offset = getPixels(child.getContext(), offsetDp);
        if (dyConsumed > offset) {
            collapse(child);
        } else if (dyUnconsumed < 0 || dyConsumed < -offset) {
            expand(child);
        }
    }

    /**
     * Returns true if the current state is scrolled up.
     */
    public boolean isScrolledUp() {
        return currentState == STATE_SCROLLED_UP;
    }


    public void expand(@NonNull V child) {
        if (isScrolledUp()) {
            return;
        }

        currentState = STATE_SCROLLED_UP;
        if(child instanceof ExtendedFloatingActionButton) {
            ((ExtendedFloatingActionButton) child).extend();
        }
    }

    /**
     * Returns true if the current state is scrolled down.
     */
    public boolean isScrolledDown() {
        return currentState == STATE_SCROLLED_DOWN;
    }

    public void collapse(@NonNull V child) {
        if (isScrolledDown()) {
            return;
        }

        currentState = STATE_SCROLLED_DOWN;
        if(child instanceof ExtendedFloatingActionButton) {
            ((ExtendedFloatingActionButton) child).shrink();
        }
    }

    private int getPixels(Context context, int dp) {
        if(context == null) {
            return 0;
        }

        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }
}

Add behavior to your layout.xml

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
                android:id="@+id/main_fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/text_analyze"
                android:layout_margin="@dimen/theme_space"
                app:icon="@drawable/ic_baseline_refresh_24"
                app:layout_behavior=".CollapseFABOnScrollBehavior"
                android:layout_gravity="bottom|end" />
Atakan Yildirim
  • 198
  • 3
  • 15