package com.steven.babyiyo.navigationbar; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorUpdateListener; import android.support.v4.view.animation.LinearOutSlowInInterpolator; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; import com.steven.babyiyo.R; import com.steven.babyiyo.navigationbar.NavigationTabBarBehavior; public class NavigationTabBarBehavior extends VerticalScrollingBehavior<NavigationTabBar> { private final static Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator(); private final static int ANIMATION_DURATION = 300; private ViewPropertyAnimatorCompat mTranslationAnimator; private ObjectAnimator mTranslationObjectAnimator; private Snackbar.SnackbarLayout mSnackbarLayout; private FloatingActionButton mFloatingActionButton; private int mSnackbarHeight = -1; private float mTargetOffset = 0, mFabTargetOffset = 0, mFabDefaultBottomMargin = 0; private boolean mHidden; private boolean mFabBottomMarginInitialized; private boolean mBehaviorTranslationEnabled = true; public NavigationTabBarBehavior(final boolean behaviorTranslationEnabled) { super(); this.mBehaviorTranslationEnabled = behaviorTranslationEnabled; } public NavigationTabBarBehavior(final Context context, final AttributeSet attrs) { super(context, attrs); final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NavigationTabBar); typedArray.recycle(); } @Override public boolean onLayoutChild(CoordinatorLayout parent, NavigationTabBar child, int layoutDirection) { return super.onLayoutChild(parent, child, layoutDirection); } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, NavigationTabBar child, View dependency) { return super.onDependentViewChanged(parent, child, dependency); } @Override public void onDependentViewRemoved(CoordinatorLayout parent, NavigationTabBar child, View dependency) { super.onDependentViewRemoved(parent, child, dependency); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, NavigationTabBar child, View dependency) { updateSnackbar(child, dependency); updateFloatingActionButton(dependency); return super.layoutDependsOn(parent, child, dependency); } @Override public void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, NavigationTabBar child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll) { } @Override public void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, NavigationTabBar child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection) { } @Override protected boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, NavigationTabBar child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection) { return false; } @Override public void onNestedScroll(CoordinatorLayout coordinatorLayout, NavigationTabBar child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); if (dyConsumed < 0) handleDirection(child, ScrollDirection.SCROLL_DIRECTION_DOWN); else if (dyConsumed > 0) handleDirection(child, ScrollDirection.SCROLL_DIRECTION_UP); } @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, NavigationTabBar child, View directTargetChild, View target, int nestedScrollAxes) { return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); } // Handle scroll direction private void handleDirection(NavigationTabBar child, int scrollDirection) { if (!mBehaviorTranslationEnabled) return; if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && mHidden) { mHidden = false; animateOffset(child, 0, false, true); } else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !mHidden) { mHidden = true; animateOffset(child, child.getHeight(), false, true); } } // Animate offset private void animateOffset(final NavigationTabBar child, final int offset, boolean forceAnimation, boolean withAnimation) { if (!mBehaviorTranslationEnabled && !forceAnimation) return; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { ensureOrCancelObjectAnimation(child, offset, withAnimation); mTranslationObjectAnimator.start(); } else { ensureOrCancelAnimator(child, withAnimation); mTranslationAnimator.translationY(offset).start(); } } // Manage animation for Android >= KITKAT private void ensureOrCancelAnimator(final NavigationTabBar child, boolean withAnimation) { if (mTranslationAnimator == null) { mTranslationAnimator = ViewCompat.animate(child); mTranslationAnimator.setDuration(withAnimation ? ANIMATION_DURATION : 0); mTranslationAnimator.setUpdateListener(new ViewPropertyAnimatorUpdateListener() { @Override public void onAnimationUpdate(View view) { // Animate snackbar if (mSnackbarLayout != null && mSnackbarLayout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { mTargetOffset = child.getBarHeight() - view.getTranslationY(); final ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) mSnackbarLayout.getLayoutParams(); p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) mTargetOffset); mSnackbarLayout.requestLayout(); } // Animate Floating Action Button if (mFloatingActionButton != null && mFloatingActionButton.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { final ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) mFloatingActionButton.getLayoutParams(); mFabTargetOffset = mFabDefaultBottomMargin - view.getTranslationY(); p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) mFabTargetOffset); mFloatingActionButton.requestLayout(); } } }); mTranslationAnimator.setInterpolator(INTERPOLATOR); } else { mTranslationAnimator.setDuration(withAnimation ? ANIMATION_DURATION : 0); mTranslationAnimator.cancel(); } } private static ObjectAnimator objectAnimatorOfTranslationY(View target, int offset) { final ObjectAnimator res; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) res = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, offset); else { res = new ObjectAnimator(); res.setTarget(target); res.setPropertyName("translationY"); res.setFloatValues(offset); } return res; } // Manage animation for Android < KITKAT private void ensureOrCancelObjectAnimation(final NavigationTabBar child, final int offset, boolean withAnimation) { if (mTranslationObjectAnimator != null) mTranslationObjectAnimator.cancel(); mTranslationObjectAnimator = objectAnimatorOfTranslationY(child, offset); mTranslationObjectAnimator.setDuration(withAnimation ? ANIMATION_DURATION : 0); mTranslationObjectAnimator.setInterpolator(INTERPOLATOR); mTranslationObjectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (mSnackbarLayout != null && mSnackbarLayout.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { mTargetOffset = child.getBarHeight() - child.getTranslationY(); final ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) mSnackbarLayout.getLayoutParams(); p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) mTargetOffset); mSnackbarLayout.requestLayout(); } // Animate Floating Action Button if (mFloatingActionButton != null && mFloatingActionButton.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { mFabTargetOffset = mFabDefaultBottomMargin - child.getTranslationY(); final ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) mFloatingActionButton.getLayoutParams(); p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) mFabTargetOffset); mFloatingActionButton.requestLayout(); } } }); } public static NavigationTabBarBehavior from(NavigationTabBar view) { final ViewGroup.LayoutParams params = view.getLayoutParams(); if (!(params instanceof CoordinatorLayout.LayoutParams)) throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); final CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior(); if (!(behavior instanceof NavigationTabBarBehavior)) throw new IllegalArgumentException( "The view is not associated with NavigationTabBarBehavior"); return (NavigationTabBarBehavior) behavior; } // Enable or not the behavior translation public void setBehaviorTranslationEnabled(boolean behaviorTranslationEnabled) { this.mBehaviorTranslationEnabled = behaviorTranslationEnabled; } // Hide NTB with animation public void hideView(NavigationTabBar view, int offset, boolean withAnimation) { if (!mHidden) { mHidden = true; animateOffset(view, offset, true, withAnimation); } } // Reset NTB position with animation public void resetOffset(NavigationTabBar view, boolean withAnimation) { if (mHidden) { mHidden = false; animateOffset(view, 0, true, withAnimation); } } // Update Snackbar bottom margin public void updateSnackbar(final NavigationTabBar child, View dependency) { if (dependency != null && dependency instanceof Snackbar.SnackbarLayout) { mSnackbarLayout = (Snackbar.SnackbarLayout) dependency; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mSnackbarLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { if (mFloatingActionButton != null && mFloatingActionButton.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { mFabTargetOffset = mFabDefaultBottomMargin - child.getTranslationY(); final ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) mFloatingActionButton.getLayoutParams(); p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, (int) mFabTargetOffset); mFloatingActionButton.requestLayout(); } } }); } if (mSnackbarHeight == -1) mSnackbarHeight = dependency.getHeight(); final int targetMargin = (int) (child.getBarHeight() - child.getTranslationY()); child.bringToFront(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { dependency.setStateListAnimator(null); dependency.setElevation(0.0F); } if (dependency.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { final ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) dependency.getLayoutParams(); p.setMargins(p.leftMargin, p.topMargin, p.rightMargin, targetMargin); dependency.requestLayout(); } } } // Update floating action button bottom margin public void updateFloatingActionButton(final View dependency) { if (dependency != null && dependency instanceof FloatingActionButton) { mFloatingActionButton = (FloatingActionButton) dependency; if (!mFabBottomMarginInitialized && dependency.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { mFabBottomMarginInitialized = true; final ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) dependency.getLayoutParams(); mFabDefaultBottomMargin = p.bottomMargin; } } } }