package io.hefuyi.listener.widget; import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorSet; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.support.annotation.NonNull; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.view.ViewCompat; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import io.hefuyi.listener.R; /** * Created by hefuyi on 2016/12/1. */ public class RotationFabBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> { private static final boolean AUTO_HIDE_DEFAULT = true; private Rect mTmpRect; private FloatingActionButton.OnVisibilityChangedListener mInternalAutoHideListener; private boolean mAutoHideEnabled; private boolean isAnimate;//动画是否在进行 public RotationFabBehavior() { super(); mAutoHideEnabled = AUTO_HIDE_DEFAULT; } public RotationFabBehavior(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, android.support.design.R.styleable.FloatingActionButton_Behavior_Layout); mAutoHideEnabled = a.getBoolean( android.support.design.R.styleable.FloatingActionButton_Behavior_Layout_behavior_autoHide, AUTO_HIDE_DEFAULT); a.recycle(); } @Override public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams lp) { if (lp.dodgeInsetEdges == Gravity.NO_GRAVITY) { // If the developer hasn't set dodgeInsetEdges, lets set it to BOTTOM so that // we dodge any Snackbars lp.dodgeInsetEdges = Gravity.BOTTOM; } } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) { if (dependency instanceof AppBarLayout) { // If we're depending on an AppBarLayout we will show/hide it automatically // if the FAB is anchored to the AppBarLayout updateFabVisibilityForAppBarLayout(parent, (AppBarLayout) dependency, child); } return false; } private boolean shouldUpdateVisibility(View dependency, FloatingActionButton child) { final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); if (!mAutoHideEnabled) { return false; } if (lp.getAnchorId() != dependency.getId()) { // The anchor ID doesn't match the dependency, so we won't automatically // show/hide the FAB return false; } //noinspection RedundantIfStatement // if (child.getVisibility() != VISIBLE) { // // The view isn't set to be visible so skip changing its visibility // return false; // } return true; } private boolean updateFabVisibilityForAppBarLayout(CoordinatorLayout parent, AppBarLayout appBarLayout, FloatingActionButton child) { if (!shouldUpdateVisibility(appBarLayout, child)) { return false; } if (mTmpRect == null) { mTmpRect = new Rect(); } // First, let's get the visible rect of the dependency final Rect rect = mTmpRect; getDescendantRect(parent, appBarLayout, rect); int height = 0; try { Method method = AppBarLayout.class.getDeclaredMethod("getMinimumHeightForVisibleOverlappingContent", (Class<?>[]) new Class[0]); method.setAccessible(true); height = (int) method.invoke(appBarLayout, new Object[0]); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } if (rect.bottom <= height) { // If the anchor's bottom is below the seam, we'll animate our FAB out if (!isAnimate&&child.getVisibility()==View.VISIBLE){ hide(child); } } else { // Else, we'll animate our FAB back in if(!isAnimate&&child.getVisibility()==View.GONE){ show(child); } } return true; } @Override public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child, int layoutDirection) { // First, let's make sure that the visibility of the FAB is consistent final List<View> dependencies = parent.getDependencies(child); for (int i = 0, count = dependencies.size(); i < count; i++) { final View dependency = dependencies.get(i); if (dependency instanceof AppBarLayout) { if (updateFabVisibilityForAppBarLayout( parent, (AppBarLayout) dependency, child)) { break; } } } // Now let the CoordinatorLayout lay out the FAB parent.onLayoutChild(child, layoutDirection); // Now offset it if needed offsetIfNeeded(parent, child); return true; } @Override public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull FloatingActionButton child, @NonNull Rect rect) { // Since we offset so that any internal shadow padding isn't shown, we need to make // sure that the shadow isn't used for any dodge inset calculations Rect shadowPadding = new Rect(0, 0, 0, 0); try { Field mShadowPadding = FloatingActionButton.class.getDeclaredField("mShadowPadding"); mShadowPadding.setAccessible(true); shadowPadding=(Rect)mShadowPadding.get(child); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } rect.set(child.getLeft() + shadowPadding.left, child.getTop() + shadowPadding.top, child.getRight() - shadowPadding.right, child.getBottom() - shadowPadding.bottom); return true; } /** * Pre-Lollipop we use padding so that the shadow has enough space to be drawn. This method * offsets our layout position so that we're positioned correctly if we're on one of * our parent's edges. */ private void offsetIfNeeded(CoordinatorLayout parent, FloatingActionButton fab) { Rect padding = new Rect(0, 0, 0, 0); try { Field mShadowPadding = FloatingActionButton.class.getDeclaredField("mShadowPadding"); mShadowPadding.setAccessible(true); padding=(Rect)mShadowPadding.get(fab); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) { final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams(); int offsetTB = 0, offsetLR = 0; if (fab.getRight() >= parent.getWidth() - lp.rightMargin) { // If we're on the right edge, shift it the right offsetLR = padding.right; } else if (fab.getLeft() <= lp.leftMargin) { // If we're on the left edge, shift it the left offsetLR = -padding.left; } if (fab.getBottom() >= parent.getHeight() - lp.bottomMargin) { // If we're on the bottom edge, shift it down offsetTB = padding.bottom; } else if (fab.getTop() <= lp.topMargin) { // If we're on the top edge, shift it up offsetTB = -padding.top; } if (offsetTB != 0) { ViewCompat.offsetTopAndBottom(fab, offsetTB); } if (offsetLR != 0) { ViewCompat.offsetLeftAndRight(fab, offsetLR); } } } static void getDescendantRect(ViewGroup parent, View descendant, Rect out) { out.set(0, 0, descendant.getWidth(), descendant.getHeight()); ViewGroupUtilsHoneycomb.offsetDescendantRect(parent, descendant, out); } private void show(final FloatingActionButton floatingActionButton) { floatingActionButton.postDelayed(new Runnable() { @Override public void run() { AnimatorSet fabAnimation = (AnimatorSet) AnimatorInflater.loadAnimator(floatingActionButton.getContext(), R.animator.float_button_in); fabAnimation.setTarget(floatingActionButton); fabAnimation.setInterpolator(new FastOutSlowInInterpolator()); fabAnimation.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { isAnimate = true; floatingActionButton.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { isAnimate = false; } @Override public void onAnimationCancel(Animator animation) { hide(floatingActionButton); } @Override public void onAnimationRepeat(Animator animation) { } }); fabAnimation.start(); } }, 150L); } private void hide(final FloatingActionButton floatingActionButton) { AnimatorSet fabAnimation = (AnimatorSet) AnimatorInflater.loadAnimator(floatingActionButton.getContext(), R.animator.float_button_out); fabAnimation.setTarget(floatingActionButton); fabAnimation.setInterpolator(new FastOutSlowInInterpolator()); fabAnimation.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { isAnimate = true; } @Override public void onAnimationEnd(Animator animation) { floatingActionButton.setVisibility(View.GONE); isAnimate = false; } @Override public void onAnimationCancel(Animator animation) { show(floatingActionButton); } @Override public void onAnimationRepeat(Animator animation) { } }); fabAnimation.start(); } static class ViewGroupUtilsHoneycomb { private static final ThreadLocal<Matrix> sMatrix = new ThreadLocal<>(); private static final ThreadLocal<RectF> sRectF = new ThreadLocal<>(); public static void offsetDescendantRect(ViewGroup group, View child, Rect rect) { Matrix m = sMatrix.get(); if (m == null) { m = new Matrix(); sMatrix.set(m); } else { m.reset(); } offsetDescendantMatrix(group, child, m); RectF rectF = sRectF.get(); if (rectF == null) { rectF = new RectF(); sRectF.set(rectF); } rectF.set(rect); m.mapRect(rectF); rect.set((int) (rectF.left + 0.5f), (int) (rectF.top + 0.5f), (int) (rectF.right + 0.5f), (int) (rectF.bottom + 0.5f)); } static void offsetDescendantMatrix(ViewParent target, View view, Matrix m) { final ViewParent parent = view.getParent(); if (parent instanceof View && parent != target) { final View vp = (View) parent; offsetDescendantMatrix(target, vp, m); m.preTranslate(-vp.getScrollX(), -vp.getScrollY()); } m.preTranslate(view.getLeft(), view.getTop()); if (!view.getMatrix().isIdentity()) { m.preConcat(view.getMatrix()); } } } }