FloatingActionButton 隐藏在列表滚动中

FloatingActionButton hide on list scroll

我正在使用 android.support.design.widget 包中的 FloatingActionButton

<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" />

是否可以将该按钮配置为在列表视图向下滚动时隐藏动画并在列表视图向上滚动到顶部时再次显示?

参见this。在这里它告诉你如何做你想要达到的目标。您必须在 CoordinatorLayoutListView 中像这样使用它:

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

根据我的说法,实现这一点的最佳方法如下。

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

查看详细答案。

使用这个 class 你可以轻松地为你的 FAB 制作动画,这里我实现了 onStopNestedScroll() 方法 来展示你的每当滚动停止时都很棒。 我使用 Handler();

将 1000 miliSeconds 设置为延迟
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" />

嘿,为了这个目的,我们可以使用带有浮动操作按钮的默认列表视图,以正常方式只对 listview.onscroll 侦听器进行修改,然后使用 recyclerview 自动隐藏浮动操作按钮我们可以感觉像回收利用

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

想要使用 recyclerview 的人可以这样做:

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

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

浮动操作按钮在向下滚动时隐藏,在向上滚动时显示。

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

我在这里为最后一个视图项目添加额外的填充以避免列表项目与浮动操作按钮重叠

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

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

现在,您只需调用带有 recyclerView 和 fab on 构造函数的 ScrollAwareFABBehavior,然后调用方法 start()。

ScrollAwareFABBehavior(recyclerView = recyclerViewPlaceFormContainer, floatingActionButton = floatingActionButton).start()

Kotlin + 数据绑定适配器

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

和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"/>

对不起!我迟到了好几年才回答这个问题。我希望这仍然可以帮助某人。这也是我的第一个回答。

朋友们!无需实现滚动侦听器。

将以下内容添加到浮动操作按钮xml:

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

给予:

<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" />

回应我的以下评论, “抱歉!我刚刚注意到这有一个奇怪的副作用。如果添加了 app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior,任何小吃店都会与这个浮动操作按钮重叠。 ☹️ 取消这条线将防止重叠,浮动操作按钮将按照协调器布局内的预期运行。 “

为了解决这个问题,请使用以下方法:

Snackbar.make(floating_action_button, "Some snackbar text!", BaseTransientBottomBar.LENGTH_SHORT).setAnchorView(floating_action_button).show();

对于 Kotlin 来说非常简单 (API 23+)

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

使用 kotlin 扩展的 recyclerView 的另一种方法。

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

现在您可以通过以下方式将 fab 附加到任何 recyclerView:

rv.attachFab(requireActivity().fab)
// in my case i made fab public on activity

补充一下,对于 NestedScrollView,方法将类似于以下内容:

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

我从 GeeksForGeeks

中获取了这段代码

您可以在 ExtendedFloatingActionButton 上使用 HideBottomViewOnScrollBehavior

<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" />

您也可以使用 Gmail 风格 collapse/expand 行为而不是隐藏 FAB。

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

为您的 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" />