编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识、前端、后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过!
FloatingActionButton的显示和隐藏其实很容易谷歌到,之所以写这篇文章是感觉这个知识点有点让人困惑,可以找到多种实现方式,而且兼容包里的FloatingActionButton还不断的变化。
基本来说,如果是使用官方的FloatingActionButton,列表滚动时的显示与隐藏都是使用自定义FloatingActionButton.Behavior来实现的。
我们先来看看比较标准的做法:
首先在xml中定义FloatingActionButton
- xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/main_content"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- android:id="@+id/appbar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
- android:id="@+id/toolbar"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- android:background="?attr/colorPrimary"
- app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
- app:layout_scrollFlags="scroll|enterAlways|snap" />
- android:id="@+id/tabs"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:tabGravity="fill"
- />
- android:id="@+id/viewpager"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:layout_behavior="@string/appbar_scrolling_view_behavior" />
- app:fabSize="normal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="end|bottom"
- android:layout_margin="@dimen/fab_margin"
- app:layout_behavior="com.jcodecraeer.fabhideandshow.behavior.ScrollAwareFABBehaviorDefault" />
我们这里使用的是比较常见的toolbar+tab+viewpager布局。
其中ScrollAwareFABBehaviorDefault是我们自定义的一个Behavior。
- public class ScrollAwareFABBehaviorDefault extends FloatingActionButton.Behavior{
- public ScrollAwareFABBehaviorDefault(Context context, AttributeSet attrs) {
- super();
- }
- @Override
- public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
- final View directTargetChild, final View target, final int nestedScrollAxes) {
- // Ensure we react to vertical scrolling
- return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
- || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
- }
- @Override
- public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
- final View target, final int dxConsumed, final int dyConsumed,
- final int dxUnconsumed, final int dyUnconsumed) {
- super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
- if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
- // User scrolled down and the FAB is currently visible -> hide the FAB
- child.hide();
- } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
- // User scrolled up and the FAB is currently not visible -> show the FAB
- child.show();
- }
- }
- }
然后我们在MainActivity里面写好主界面的其它代码-从github api获取数据,显示列表。Fab在列表滚动时候的显示与隐藏就完成了。看效果
为什么说这个方法比较官方呢?这是因为在fab显示与隐藏时使用的动画直接借用了FloatingActionButton内置的动画效果,直接在条件恰当的时候调用hide()和show()方法。这两个方法是在22.2.1版本才加进去的,在这之前需要自己写。具体写法参见:http://stackoverflow.com/questions/31381474/menu-and-autohide-floatingactionbutton-of-android-design-support-library 。
然而我并不喜欢这种效果,我更喜欢上下移动的动画方式。
所以我们只能自己定义动画效果了,其实也很简单:
- public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
- private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
- private boolean mIsAnimatingOut = false;
- public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
- super();
- }
- @Override
- public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
- final View directTargetChild, final View target, final int nestedScrollAxes) {
- // Ensure we react to vertical scrolling
- return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
- || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
- }
- @Override
- public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
- final View target, final int dxConsumed, final int dyConsumed,
- final int dxUnconsumed, final int dyUnconsumed) {
- super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
- if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
- // User scrolled down and the FAB is currently visible -> hide the FAB
- animateOut(child);
- } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
- // User scrolled up and the FAB is currently not visible -> show the FAB
- animateIn(child);
- }
- }
- // Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits
- private void animateOut(final FloatingActionButton button) {
- if (Build.VERSION.SDK_INT >= 14) {
- ViewCompat.animate(button).translationY(button.getHeight() + getMarginBottom(button)).setInterpolator(INTERPOLATOR).withLayer()
- .setListener(new ViewPropertyAnimatorListener() {
- public void onAnimationStart(View view) {
- ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
- }
- public void onAnimationCancel(View view) {
- ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
- }
- public void onAnimationEnd(View view) {
- ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
- view.setVisibility(View.GONE);
- }
- }).start();
- } else {
- }
- }
- // Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters
- private void animateIn(FloatingActionButton button) {
- button.setVisibility(View.VISIBLE);
- if (Build.VERSION.SDK_INT >= 14) {
- ViewCompat.animate(button).translationY(0)
- .setInterpolator(INTERPOLATOR).withLayer().setListener(null)
- .start();
- } else {
- }
- }
- private int getMarginBottom(View v) {
- int marginBottom = 0;
- final ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
- if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
- marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
- }
- return marginBottom;
- }
- }
效果如下:
好了,前两种其实都是比较推崇的方式,只是效果不同而已。
下面这种方式也可以实现fab的显示与隐藏,动画接近于上面的第二种效果,但是并不推崇这种方式。
它是这样定义Behavior的:
- public class ScrollAwareFABBehaviorDepend extends FloatingActionButton.Behavior{
- private int toolbarHeight;
- public ScrollAwareFABBehaviorDepend(Context context, AttributeSet attrs) {
- super();
- this.toolbarHeight = getToolbarHeight(context);
- }
- @Override
- public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
- return super.layoutDependsOn(parent, fab, dependency) || (dependency instanceof AppBarLayout);
- }
- @Override
- public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
- boolean returnValue = super.onDependentViewChanged(parent, fab, dependency);
- if (dependency instanceof AppBarLayout) {
- CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
- int fabBottomMargin = lp.bottomMargin;
- int distanceToScroll = fab.getHeight() + fabBottomMargin;
- float ratio = (float)dependency.getY()/(float)toolbarHeight;
- fab.setTranslationY(-distanceToScroll * ratio);
- }
- return returnValue;
- }
- public static int getToolbarHeight(Context context) {
- final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
- new int[]{R.attr.actionBarSize});
- int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
- styledAttributes.recycle();
- return toolbarHeight;
- }
- }
它的显示与隐藏是根据AppBarLayout的Y值来决定的,我们知道如果按照最上面的方式定义主界面布局,列表滚动的时候toolbar会显示和隐藏,而toolbar是AppBarLayout的一部分,因此可以让Behavior依赖于AppBarLayout,当AppBarLayout变化的时候会调用onDependentViewChanged,然后在这里获取AppBarLayout的高度移动的距离,然后根据这个距离来判定FloatingActionButton上下移动的距离,从而实现了FloatingActionButton的显示和隐藏。这个实现方式我是在这里找到的:http://stackoverflow.com/questions/31457099/android-fab-to-hide-when-navigating-between-different-fragments-in-a-viewpager
代码很简单,但是我并不推崇这种方式,原因:
1.由于只是简单的移动了FloatingActionButton的Y值,效果并不好。
gif图几乎看不出来和上一种方式的区别。
2.FloatingActionButton依赖于AppBarLayout,如果没有AppBarLayout则会出问题,而且即使有AppBarLayout如果AppBarLayout不会变化也会失效。
3.从逻辑上说FloatingActionButton的显示与隐藏本应该是依赖于列表的滚动的,但是这里却是依赖于AppBarLayout,实际上是列表的滚动让AppBarLayout发生变化,然后AppBarLayout再去通知fab。
不过如果你坚持使用这种方法也没有大问题。