FloatingActionButton的显示和隐藏其实很容易谷歌到,之所以写这篇文章是感觉这个知识点有点让人困惑,可以找到多种实现方式,而且兼容包里的FloatingActionButton还不断的变化。

基本来说,如果是使用官方的FloatingActionButton,列表滚动时的显示与隐藏都是使用自定义FloatingActionButton.Behavior来实现的。

NestedScroll的实现方式

我们先来看看比较标准的做法:

首先在xml中定义FloatingActionButton<?xml  version="1.0" encoding="utf-8"?>
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 
// 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 
// 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;
}
}

效果如下:

好了,前两种其实都是比较推崇的方式,只是效果不同而已。

Depend的实现方式

下面这种方式也可以实现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。

不过如果你坚持使用这种方法也没有大问题。