【问题标题】:Hide FloatingActionButton on scroll of RecyclerView在 RecyclerView 的滚动上隐藏 FloatingActionButton
【发布时间】:2016-01-17 10:58:54
【问题描述】:

我想在RecyclerView 的滚动条上隐藏/显示FloatingActionButton

我的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() 在我滚动列表时不会被调用。

【问题讨论】:

  • 有人找到解决方案了吗?

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


【解决方案1】:

最简单的解决方案:

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

【讨论】:

  • 我已经尝试过使用代码路径指南,但我浪费了一些时间。这是最简单的一个
  • 这会在滚动时隐藏 FAB。但是当您停止滚动时会显示 fab。向下滚动时如何隐藏它并隐藏直到您向上滚动?
  • 这似乎在滚动时隐藏,但如果你飞行它会隐藏并重新出现两次
【解决方案2】:

这应该适合你:

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

【讨论】:

  • 很好的解决方案,谢谢
【解决方案3】:

好的,这就是你需要的:

首先,由于您的 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进行了测试

【讨论】:

  • 还是不行。是不是因为我的支持库已经过时了?
  • 如果可能的话,你能给我提供一个可行的例子吗?
  • @Pritam Kadam 查看我的更新答案以获得完整的解决方案。
  • 我们的 ScrollAwareFABBehavior 看起来完全一样,但它仍然无法正常工作,我的支持库版本是 23.1
  • 您的问题一定出在其他地方,也许是 DrawerLayout
【解决方案4】:

短而简单的解决方案:

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

【讨论】:

  • 感谢@PriyaRajan glade 它帮助了你:)
【解决方案5】:

如果您使用 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" 
            ... />

【讨论】:

  • 这很好,但请记住,如果您不将小吃店锚定到您的工厂,这将与您的小吃店重叠。
【解决方案6】:

我就是这样做的。这个对我有用!如果你不知道如何实现,你可以在这个链接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;
    }
}

【讨论】:

    【解决方案7】:

    如果您没有使用协调器布局,并且想要平滑地隐藏和显示 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】:

      我创建了一个 自定义 RecyclerView,它已准备好 OnUpDownScrollListenerOnLeftRightScrollListener: p>

      代码:

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

      同样你可以通过设置来处理左右滚动

      setOnLeftRightScrollListener
      

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

      【讨论】:

        【解决方案9】:

        解决办法在:F.A.B Hides but Doesn't Show

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

        【讨论】:

          【解决方案10】:

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

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

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

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

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

            【讨论】:

              【解决方案12】:

              //lv = 列表视图

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

              【讨论】:

              • 当给出答案总是很好的时候,添加一些解释而不是仅仅发布一个没有任何信息的原始 sn-p。
              【解决方案13】:

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

              【讨论】:

                【解决方案14】:

                有点晚了,但对我有用。

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

                scrollY 大于oldScrollY 时,这意味着用户已经向下滚动,所以我们只需要检查 FAB 是否显示。如果是,我们将其隐藏。

                scrollY 小于oldScrollY 表示向上滚动。我们检查 FAB 是否仍然隐藏以显示它。

                【讨论】:

                • 请添加更多详细信息以扩展您的答案,例如工作代码或文档引用。
                【解决方案15】:

                试试这个

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

                享受吧。

                【讨论】:

                  猜你喜欢
                  • 2015-10-15
                  • 2016-06-17
                  • 2019-07-05
                  • 2015-05-01
                  • 2023-01-17
                  • 2021-06-17
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多