package mao.com.mao_wanandroid_client.widget.layoutbehavior; import android.annotation.SuppressLint; import android.content.Context; import android.support.annotation.NonNull; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorListener; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.animation.Interpolator; /** * @author maoqitian * @Description: 自定义 FloatingActionButton 滑动显示与隐藏 结合 RecyclerView * 使用前提是 fab 包含在 CoordinatorLayout中 * @date 2019/6/21 0021 9:17 */ public class ScrollAwareFABBehavior2 extends CoordinatorLayout.Behavior<FloatingActionButton> { private static final String TAG = "ScrollAwareFABBehavior2"; private boolean fabAnimationStarted = false; //是否开始 fab 动画 private boolean flingHappened = false; private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); private boolean mIsAnimatingOut = false; //必需实现该构造方法,CoordinatorLayout源码中parseBehavior()函数中直接反射调用这个构造函数 public ScrollAwareFABBehavior2(Context context, AttributeSet attrs) { super(context, attrs); } // 如果是 RecyclerView 拦截嵌套滑动 @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) { // 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; } //进行滑动事件处理 /** * 接收子View处理完滑动后的滑动距离信息, 在这里父控件可以选择是否处理剩余的滑动距离。如果想要该方法得到回调,先前的onStartNestedScroll(View, View, int, int)必须返回true * @param coordinatorLayout * @param child * @param target 触发嵌套滑动的 view * @param dxConsumed 表示 view 消费了 x 方向的距离 * @param dyConsumed 表示 view 消费了 y 方向的距离 * @param dxUnconsumed 表示 view 剩余未消费 x 方向距离 * @param dyUnconsumed 表示 view 剩余未消费 y 方向距离 * @param type 触发滑动事件的类型:其值有 * ViewCompat. TYPE_TOUCH * ViewCompat. TYPE_NON_TOUCH */ @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); } /** * 在子View消费滑动事件前,优先响应滑动操作,消费部分或全部滑动距离,提前处理执行动画 * @param coordinatorLayout * @param child * @param target 触发嵌套滑动的 view * @param dx 表示 view 本次 x 方向的滚动的总距离,单位:像素 * @param dy 表示 view 本次 y 方向的滚动的总距离,单位:像素 * @param consumed * @param type 触发滑动事件的类型:其值有 * ViewCompat. TYPE_TOUCH * ViewCompat. TYPE_NON_TOUCH */ @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();*/ if (dy > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) { // 不显示FAB animateOut(child); } else if (dy < 0 && child.getVisibility() != View.VISIBLE) { // 显示FAB animateIn(child); } if(target instanceof RecyclerView && child.getVisibility() == View.VISIBLE){ //RecyclerView 滑动到顶部隐藏 fab RecyclerView recyclerView = (RecyclerView) target; //直接设置监听事件让 recyclerView 回到顶部 child.setOnClickListener(v -> recyclerView.smoothScrollToPosition(0)); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { //newState分 0,1,2三个状态,2是滚动状态,0是停止 super.onScrollStateChanged(recyclerView, newState); //-1代表顶部,返回true表示没到顶,还可以滑 //1代表底部,返回true表示没到底部,还可以滑 boolean b = recyclerView.canScrollVertically(-1); if(!b){ // 不显示FAB animateOut(child); } } }); } } } // 定义滑动时的属性动画效果 隐藏 @SuppressLint("RestrictedApi") private void animateOut(final FloatingActionButton button) { ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer() .setListener(new ViewPropertyAnimatorListener() { public void onAnimationStart(View view) { ScrollAwareFABBehavior2.this.mIsAnimatingOut = true; } public void onAnimationCancel(View view) { ScrollAwareFABBehavior2.this.mIsAnimatingOut = false; } public void onAnimationEnd(View view) { ScrollAwareFABBehavior2.this.mIsAnimatingOut = false; view.setVisibility(View.INVISIBLE); } }).start(); } //显示 @SuppressLint("RestrictedApi") private void animateIn(FloatingActionButton button) { button.setVisibility(View.VISIBLE); ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F) .setInterpolator(INTERPOLATOR).withLayer().setListener(null) .start(); } }