Java tutorial
package com.luseen.luseenbottomnavigation.BottomNavigation.behavior; /* * BottomNavigationLayout library for Android * Copyright (c) 2016. Nikola Despotoski (http://github.com/NikolaDespotoski). * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * */ import android.content.Context; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; 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.ViewPropertyAnimatorListenerAdapter; import android.support.v4.view.ViewPropertyAnimatorUpdateListener; import android.support.v4.view.animation.LinearOutSlowInInterpolator; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.animation.Interpolator; import com.luseen.luseenbottomnavigation.BottomNavigation.BottomNavigationView; import java.util.HashMap; import static android.util.Log.DEBUG; import static android.util.Log.ERROR; import static android.util.Log.INFO; import static android.util.Log.VERBOSE; import static android.util.Log.WARN; /** * Created by Nikola D. on 3/15/2016. */ public final class BottomNavigationBehavior<V extends View> extends VerticalScrollingBehavior<V> { private static final Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator(); private static final String TAG = "BottomNavigationBehavior"; private boolean hidden = false; private ViewPropertyAnimatorCompat mOffsetValueAnimator; private int mSnackbarHeight = -1; private boolean scrollingEnabled = true; private boolean hideAlongSnackbar = false; /** * current Y offset */ private int offset; private int scaledTouchSlop = 16; //Default, from ViewConfiguration.java PAGING_TOUCH_SLOP final HashMap<View, DependentView> dependentViewHashMap = new HashMap<>(); FabDependentView fabDependentView; SnackBarDependentView snackbarDependentView; private OnExpandStatusChangeListener listener; private int height; private int bottomInset; private boolean translucentNavigation; private int maxOffset; public BottomNavigationBehavior() { super(); } public BottomNavigationBehavior(Context context, AttributeSet attrs) { super(context, attrs); scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2; } public static <V extends View> BottomNavigationBehavior<V> from(@NonNull V view) { ViewGroup.LayoutParams params = view.getLayoutParams(); if (!(params instanceof CoordinatorLayout.LayoutParams)) { throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); } CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior(); if (!(behavior instanceof BottomNavigationBehavior)) { throw new IllegalArgumentException("The view is not associated with BottomNavigationBehavior"); } return (BottomNavigationBehavior<V>) behavior; } public void setLayoutValues(final int bottomNavHeight, final int bottomInset) { log(TAG, INFO, "setLayoutValues(%d, %d)", bottomNavHeight, bottomInset); this.height = bottomNavHeight; this.bottomInset = bottomInset; this.translucentNavigation = bottomInset > 0; this.maxOffset = height + (translucentNavigation ? bottomInset : 0); //log(TAG, DEBUG, "height: %d, translucent: %b, maxOffset: %d, bottomInset: %d", height, translucentNavigation, maxOffset, bottomInset); } public void show(CoordinatorLayout coordinatorLayout, V child) { if (hidden) { offset = 0; hidden = false; animateOffset(coordinatorLayout, child, 0); } } protected boolean isFloatingActionButton(View dependency) { return FloatingActionButton.class.isInstance(dependency); } protected boolean isSnackBar(View dependency) { return Snackbar.SnackbarLayout.class.isInstance(dependency); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return isFloatingActionButton(dependency) || isSnackBar(dependency); } @Override public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) { log(TAG, ERROR, "onDependentViewRemoved(%s)", dependency.getClass().getSimpleName()); if (isFloatingActionButton(dependency)) { fabDependentView = null; } else if (Snackbar.SnackbarLayout.class.isInstance(dependency)) { snackbarDependentView = null; if (null != fabDependentView) { fabDependentView.onDependentViewChanged(parent, child); } } final DependentView dependent = dependentViewHashMap.remove(dependency); log(TAG, ERROR, "removed: %s", dependent); if (null != dependent) { dependent.onDestroy(); } } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) { boolean isFab = isFloatingActionButton(dependency); boolean isSnackBack = isSnackBar(dependency); DependentView dependent; if (!dependentViewHashMap.containsKey(dependency)) { if (!isFab && !isSnackBack) { dependent = new GenericDependentView(dependency, height, bottomInset); } else if (isFab) { dependent = new FabDependentView(dependency, height, bottomInset); fabDependentView = (FabDependentView) dependent; } else { dependent = new SnackBarDependentView((Snackbar.SnackbarLayout) dependency, height, bottomInset); snackbarDependentView = (SnackBarDependentView) dependent; } dependentViewHashMap.put(dependency, dependent); } else { dependent = dependentViewHashMap.get(dependency); } if (null != dependent) { return dependent.onDependentViewChanged(parent, child); } return true; } @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) { offset = 0; return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); } @Override public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) { super.onStopNestedScroll(coordinatorLayout, child, target); offset = 0; } @Override public void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll) { } @Override public void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection) { log(TAG, INFO, "Scroll x: %d - y: %d", dx, dy); offset += dy; if (offset < -scaledTouchSlop) { handleDirection(coordinatorLayout, child, scrollDirection); offset = 0; } else if (offset > scaledTouchSlop) { handleDirection(coordinatorLayout, child, scrollDirection); offset = 0; } } private void handleDirection(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int scrollDirection) { if (!scrollingEnabled) return; if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && hidden) { hidden = false; animateOffset(coordinatorLayout, child, 0); } else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !hidden) { hidden = true; animateOffset(coordinatorLayout, child, child.getHeight()); } } @Override protected boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection) { if (Math.abs(velocityY) > 1000) { handleDirection(coordinatorLayout, child, scrollDirection); } return true; } private void animateOffset(CoordinatorLayout coordinatorLayout, final V child, final int offset) { ensureOrCancelAnimator(coordinatorLayout, child); mOffsetValueAnimator.translationY(offset).start(); } Handler handler = new Handler(Looper.getMainLooper()); private void ensureOrCancelAnimator(final CoordinatorLayout coordinatorLayout, final V child) { if (mOffsetValueAnimator == null) { mOffsetValueAnimator = ViewCompat.animate(child); mOffsetValueAnimator.setDuration( coordinatorLayout.getResources().getInteger(android.R.integer.config_shortAnimTime)); mOffsetValueAnimator.setInterpolator(INTERPOLATOR); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mOffsetValueAnimator.setUpdateListener(new ViewPropertyAnimatorUpdateListener() { @Override public void onAnimationUpdate(View view) { if (null != fabDependentView) { fabDependentView.onDependentViewChanged(coordinatorLayout, child); } if (null != snackbarDependentView) { snackbarDependentView.onDependentViewChanged(coordinatorLayout, child); } } }); } else { updateDependentViewsRunnable.setViews(coordinatorLayout, child); mOffsetValueAnimator.setListener(new ViewPropertyAnimatorListenerAdapter() { @Override public void onAnimationStart(View view) { handler.postDelayed(updateDependentViewsRunnable, 16); } @Override public void onAnimationEnd(View view) { handler.removeCallbacks(updateDependentViewsRunnable); } @Override public void onAnimationCancel(View view) { handler.removeCallbacks(updateDependentViewsRunnable); } }); } } else { mOffsetValueAnimator.cancel(); } } public AnimationRunnable updateDependentViewsRunnable = new AnimationRunnable() { @Override public void run() { if (null != fabDependentView) { fabDependentView.onDependentViewChanged(coordinatorLayout, child); } if (null != snackbarDependentView) { snackbarDependentView.onDependentViewChanged(coordinatorLayout, child); } handler.postDelayed(this, 16); } }; private abstract static class AnimationRunnable implements Runnable { CoordinatorLayout coordinatorLayout; View child; protected void setViews(CoordinatorLayout coordinatorLayout, View child) { this.coordinatorLayout = coordinatorLayout; this.child = child; } } private interface BottomNavigationWithSnackbar { void updateSnackbar(CoordinatorLayout parent, View dependency, View child); } abstract static class DependentView<V extends View> { final V child; final ViewGroup.MarginLayoutParams layoutParams; final int bottomMargin; final int height; final int bottomInset; DependentView(V child, final int height, final int bottomInset) { this.child = child; this.layoutParams = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); this.bottomMargin = layoutParams.bottomMargin; this.height = height; this.bottomInset = bottomInset; } void onDestroy() { } abstract boolean onDependentViewChanged(CoordinatorLayout parent, View navigation); } static class GenericDependentView extends DependentView<View> { private static final String TAG = BottomNavigationBehavior.TAG + "." + GenericDependentView.class.getSimpleName(); GenericDependentView(final View child, final int height, final int bottomInset) { super(child, height, bottomInset); log(TAG, INFO, "new GenericDependentView(%s)", child.getClass().getSimpleName()); } @Override void onDestroy() { } @Override boolean onDependentViewChanged(final CoordinatorLayout parent, final View navigation) { log(TAG, VERBOSE, "onDependentViewChanged"); layoutParams.bottomMargin = bottomMargin + height; return true; } } private static class FabDependentView extends DependentView<View> { private static final String TAG = BottomNavigationBehavior.TAG + "." + FabDependentView.class.getSimpleName(); FabDependentView(final View child, final int height, final int bottomInset) { super(child, height, bottomInset); log(TAG, INFO, "new FabDependentView"); } @Override boolean onDependentViewChanged(final CoordinatorLayout parent, final View navigation) { final float t = Math.max(0, navigation.getTranslationY() - height); log(TAG, DEBUG, "translationY: %f", navigation.getTranslationY()); int transY = (int) navigation.getTranslationY(); if (bottomInset > 0) { layoutParams.bottomMargin = (int) (bottomMargin + height - t); } else { layoutParams.bottomMargin = bottomMargin + height - transY; } child.requestLayout(); return true; } @Override void onDestroy() { } } private static class SnackBarDependentView extends DependentView<Snackbar.SnackbarLayout> { private static final String TAG = BottomNavigationBehavior.TAG + "." + SnackBarDependentView.class.getSimpleName(); private int snackbarHeight = -1; SnackBarDependentView(final Snackbar.SnackbarLayout child, final int height, final int bottomInset) { super(child, height, bottomInset); } @Override boolean onDependentViewChanged(final CoordinatorLayout parent, final View navigation) { log(TAG, VERBOSE, "onDependentViewChanged"); if (Build.VERSION.SDK_INT < 21) { int index1 = parent.indexOfChild(child); int index2 = parent.indexOfChild(navigation); if (index1 > index2) { log(TAG, WARN, "swapping children"); navigation.bringToFront(); } } //scrollEnabled = false; final boolean expanded = navigation.getTranslationY() == 0; if (snackbarHeight == -1) { snackbarHeight = child.getHeight(); } log(TAG, VERBOSE, "snack.height:%d, nav.height: %d, snack.y:%g, nav.y:%g, expanded: %b", snackbarHeight, height, child.getTranslationY(), navigation.getTranslationY(), expanded); final float maxScroll = Math.max(0, navigation.getTranslationY() - bottomInset); layoutParams.bottomMargin = (int) (height - maxScroll); child.requestLayout(); return true; } @Override void onDestroy() { log(TAG, INFO, "onDestroy"); //scrollEnabled = true; } } public interface OnExpandStatusChangeListener { void onExpandStatusChanged(boolean expanded, final boolean animate); } public static void log(final String tag, final int level, String message, Object... arguments) { if (BottomNavigationView.DEBUG) { Log.println(level, tag, String.format(message, arguments)); } } }