Hide FloatingActionButton on scroll of RecyclerVie

2019-01-21 19:20发布

问题:

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.

回答1:

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



回答2:

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


回答3:

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


回答4:

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


回答5:

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;
    }
}


回答6:

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 :)



回答7:

The solution is in: F.A.B Hides but Doesn't Show

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



回答8:

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


回答9:

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


回答10:

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

    }
}
}


回答11:

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.