先贴 Behavior 的代码,这个 Behavior 的效果是上滑消失下滑显示,呈现效果是缩放隐藏,展开出现,这是题外话。
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) {
// 确定是在垂直方向上滑动
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) {
// 不显示FAB
animateOut(child);
}
else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
// 显示FAB
animateIn(child);
}
}
// 定义滑动时的属性动画效果
private void animateOut(final FloatingActionButton button) {
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F).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 {
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fade_out_end);
anim.setInterpolator(INTERPOLATOR);
anim.setDuration(200L);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
}
public void onAnimationEnd(Animation animation) {
ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
button.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(final Animation animation) {
}
});
button.startAnimation(anim);
}
}
private void animateIn(FloatingActionButton button) {
button.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
.setInterpolator(INTERPOLATOR).withLayer().setListener(null)
.start();
}
else {
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_anim);
anim.setDuration(200L);
anim.setInterpolator(INTERPOLATOR);
button.startAnimation(anim);
}
}
}
因为项目需求要跟进 Android 7.1 的新特性,所以就跟着需求走,把 SDK 改成了25,然后就出现了题目中所说的问题,上滑隐藏,接下来不管怎么滑都无法显示出来了,带着疑问上网各种找,好不容易找到一篇相关的,当时作者给的解决方案是改回24,但明显不符合我的要求,然后作者还给出了问题的卡住点,是 Behavior 里面的 onNestedScroll 方法,然后我切换了24 以及 25 两个版本的SDK分别进行了调试,才发现确实是这样的,24 SDK时不管上滑下滑均会调用到这个方法,但是25 SDK起就只会跑两次,后来不管怎么滑都无法调用那个方法。
那么是否是因为有新的方法替代了呢?翻了翻对应的方法,发现并没有,方法没有过期,也没有出现新的方法。
瞬间就蒙了,切回24那我7.1的新特性就无法实现了,接下来我检查了下Behavior所用到的库,发现用到了 design 库,然后我就想会不会是这个库的版本的问题?
去谷歌那找了这个库的历史版本,发现25 SDK已经有了四个版本:25.0.0,25.0.1,25.1.0,25.1.1。
当时项目里面用的是最新的25.1.1,我试着单独把 design 库的版本切回25.0.0,重新 run 一遍,发现 Behavior 居然正常工作了!然后我又切了25.0.1也可以,但当我切到25.1.0以及25.1.1的时候又不行了。
所以初步断定这个可能是25.1.0和25.1.1两个版本的 design 库的 bug。
总结:解决方法是单独把 design 库切回25.0.0或者25.0.1版本。
2017.6.14更新解决方案:
因为26的 SDK 已经出来了,这个问题会因为更新26的 SDK 而继续存在,所以必须得查找新的解决方案。
上次说到 Behavior 里面的 onNestedScroll 方法无法再次调用的问题,后来上网查了相关资料,发现有人提及了 CoordinatorLayout 的 onNestedScroll() 方法在24 SDK 和25 SDK 中有小差别:
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
final int childCount = getChildCount();
boolean accepted = false;
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
accepted = true;
}
}
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
if (view.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
那么解决方法很简单,不要用 hide() 和 setVisibility(View.GONE) 方法,可以改用 setVisibility(View.INVISIBLE) 方法;或者调整为将其滑出屏幕显示范围来达到隐藏的效果。
调整后的 Behavior 代码如下:
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) {
// 确定是在垂直方向上滑动
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) {
// 不显示FAB
animateOut(child);
}
else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
// 显示FAB
animateIn(child);
}
}
// 定义滑动时的属性动画效果
private void animateOut(final FloatingActionButton button) {
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F).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.INVISIBLE);
}
}).start();
}
else {
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fade_out_end);
anim.setInterpolator(INTERPOLATOR);
anim.setDuration(200L);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
}
public void onAnimationEnd(Animation animation) {
ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
button.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationRepeat(final Animation animation) {
}
});
button.startAnimation(anim);
}
}
private void animateIn(FloatingActionButton button) {
button.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
.setInterpolator(INTERPOLATOR).withLayer().setListener(null)
.start();
}
else {
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_anim);
anim.setDuration(200L);
anim.setInterpolator(INTERPOLATOR);
button.startAnimation(anim);
}
}
}