在RecyclerView的滚动上隐藏FloatingActionButton

时间:2015-10-19 07:14:48

标签: scroll android-recyclerview floating-action-button android-coordinatorlayout

我想隐藏/显示FloatingActionButton滚动RecyclerView

我的XML布局:

<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是此布局的父布局。

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

FloatingActionButton使用此布局行为。

当我看到logcat时,只有构造函数被调用。滚动列表时,onNestedScroll()没有被调用。

14 个答案:

答案 0 :(得分:89)

最简单的解决方案:

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

答案 1 :(得分:33)

这应该适合你:

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

答案 2 :(得分:14)

好的,这就是你需要的:

首先,由于您的FAB取决于RecyclerView,请将以下内容添加到您的行为类中:

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

    return false;
}

接下来,要接收onNestedScroll()次来电,您需要覆盖此内容:

 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;
}
祝你好运!

<强>更新

以下是ScrollAwareFABBehavior应该是这样的内容:

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

此外,它使用com.android.support:design:23.0.1

进行了测试

答案 3 :(得分:9)

短而简单的解决方案:

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

答案 4 :(得分:3)

这就是我的方式。这个对我有用!如果您不知道如何实施,可以在此链接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;
    }
}

答案 5 :(得分:1)

我创建了 自定义RecyclerView ,其 OnUpDownScrollListener OnLeftRightScrollListener 准备就绪:

代码:

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

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

同样可以通过设置

来处理LeftRight滚动
setOnLeftRightScrollListener

我希望它可以帮助某人:)

答案 6 :(得分:1)

解决方案位于:F.A.B Hides but Doesn't Show

问题是Android 25.0.x +将视图设置为GONE,这就是侦听器未报告更改的原因。

答案 7 :(得分:1)

如果您不使用协调器布局,并且想要平滑地隐藏和显示FAB。而且您想要实现自己的逻辑,以在向下滚动时隐藏fab,并在向上滚动时显示它。

然后这是Kotlin中的解决方案,

  • 声明变量,
var scrollingDown = false
  • 之后,创建一个侦听器,
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
                        )
                    )
                }

            }
        })
  • 创建动画资源文件

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"

答案 8 :(得分:1)

如果您使用的是Material Components for Android,而您的FAB位于CoordinatorLayout内,则可以使用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" 
            ... />

答案 9 :(得分:0)

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

答案 10 :(得分:0)

我在RecyclerView.Adapter的onBindViewHolder方法中使用它将列表中最后一项的下边距设置为72dp,以便它在浮动操作按钮上方向上滚动。

这不需要列表中的虚拟条目。

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

答案 11 :(得分:0)

行为路径和onNestedScroll(而不是recyclerview监听器)上的所有答案都没有评论在滚动时多次调用onNestedScroll的事实。这意味着child.show()和child.hide()也会被多次调用。尽管show()和hide()旨在处理这种情况,但它们仍会运行大量代码并创建一些对象,这些对象乘以onNestedScroll的次数被调用,导致不必要地创建大量对象。

考虑到这一点,因为我想运行一个不同的动画而不是默认的show()和hide(),我提出了以下行为实现:

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

    }
}
}

答案 12 :(得分:0)

滚动时,浮动操作按钮将隐藏,滚动停止时将显示。

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

答案 13 :(得分:-1)

试试这个

session

享受。