Skip to content

Instantly share code, notes, and snippets.

@NikolaDespotoski
Last active December 19, 2022 06:14
Show Gist options
  • Save NikolaDespotoski/1d6fef4949eb9be05a46 to your computer and use it in GitHub Desktop.
Save NikolaDespotoski/1d6fef4949eb9be05a46 to your computer and use it in GitHub Desktop.
Bottom Navigation behavior
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinatorlayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_favorites"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/long_text" />
<TextView
android:id="@+id/text_schedules"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/text_schedules"
android:visibility="gone" />
<TextView
android:id="@+id/text_music"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/text_music"
android:visibility="gone" />
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:itemBackground="@color/colorPrimary"
app:itemIconTint="@color/white"
app:itemTextColor="@color/white"
app:layout_behavior=".BottomNavigationBehavior"
app:menu="@menu/bottom_navigation_main" />
</android.support.design.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BottomNavigationBehavior_Params">
<attr name="tabLayoutId" format="reference"></attr>
</declare-styleable>
</resources>
/*
* 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.
*
*
*/
package bottomnav.hitherejoe.com.bottomnavigationsample;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
/**
* 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 final BottomNavigationWithSnackbar mWithSnackBarImpl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? new LollipopBottomNavWithSnackBarImpl() : new PreLollipopBottomNavWithSnackBarImpl();
private boolean isTablet;
private int mTabLayoutId;
private boolean hidden = false;
private ViewPropertyAnimatorCompat mOffsetValueAnimator;
private ViewGroup mTabLayout;
private View mTabsHolder;
private int mSnackbarHeight = -1;
private boolean scrollingEnabled = true;
private boolean hideAlongSnackbar = false;
int[] attrsArray = new int[] {
android.R.attr.id };
public BottomNavigationBehavior() {
super();
}
public BottomNavigationBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
attrsArray);
mTabLayoutId = a.getResourceId(0, View.NO_ID);
a.recycle();
}
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;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
mWithSnackBarImpl.updateSnackbar(parent, dependency, child);
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
updateScrollingForSnackbar(dependency, child, true);
super.onDependentViewRemoved(parent, child, dependency);
}
private void updateScrollingForSnackbar(View dependency, V child, boolean enabled) {
if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) {
scrollingEnabled = enabled;
if (!hideAlongSnackbar && ViewCompat.getTranslationY(child) != 0) {
ViewCompat.setTranslationY(child, 0);
hidden = false;
hideAlongSnackbar = true;
}else if(hideAlongSnackbar){
hidden = true;
animateOffset(child, -child.getHeight());
}
}
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
updateScrollingForSnackbar(dependency, child, false);
return super.onDependentViewChanged(parent, child, dependency);
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
boolean layoutChild = super.onLayoutChild(parent, child, layoutDirection);
if (mTabLayout == null && mTabLayoutId != View.NO_ID) {
mTabLayout = findTabLayout(child);
getTabsHolder();
}
return layoutChild;
}
@Nullable
private ViewGroup findTabLayout(@NonNull View child) {
if (mTabLayoutId == 0) return null;
return (ViewGroup) child.findViewById(mTabLayoutId);
}
@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) {
handleDirection(child, scrollDirection);
}
private void handleDirection(V child, @ScrollDirection int scrollDirection) {
if (!scrollingEnabled) return;
if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && hidden) {
hidden = false;
animateOffset(child, 0);
} else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !hidden) {
hidden = true;
animateOffset(child, child.getHeight());
}
}
@Override
protected boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection) {
handleDirection(child, scrollDirection);
return true;
}
private void animateOffset(final V child, final int offset) {
ensureOrCancelAnimator(child);
mOffsetValueAnimator.translationY(offset).start();
animateTabsHolder(offset);
}
private void animateTabsHolder(int offset) {
if (mTabsHolder != null) {
offset = offset > 0 ? 0 : 1;
ViewCompat.animate(mTabsHolder).alpha(offset).setDuration(200).start();
}
}
private void ensureOrCancelAnimator(V child) {
if (mOffsetValueAnimator == null) {
mOffsetValueAnimator = ViewCompat.animate(child);
mOffsetValueAnimator.setDuration(100);
mOffsetValueAnimator.setInterpolator(INTERPOLATOR);
} else {
mOffsetValueAnimator.cancel();
}
}
private void getTabsHolder() {
if (mTabLayout != null) {
mTabsHolder = mTabLayout.getChildAt(0);
}
}
public boolean isScrollingEnabled() {
return scrollingEnabled;
}
public void setScrollingEnabled(boolean scrollingEnabled) {
this.scrollingEnabled = scrollingEnabled;
}
public void setHidden(V view, boolean bottomLayoutHidden) {
if (!bottomLayoutHidden && hidden) {
animateOffset(view, 0);
} else if (bottomLayoutHidden && !hidden) {
animateOffset(view, -view.getHeight());
}
hidden = bottomLayoutHidden;
}
private interface BottomNavigationWithSnackbar {
void updateSnackbar(CoordinatorLayout parent, View dependency, View child);
}
private class PreLollipopBottomNavWithSnackBarImpl implements BottomNavigationWithSnackbar {
@Override
public void updateSnackbar(CoordinatorLayout parent, View dependency, View child) {
if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) {
if (mSnackbarHeight == -1) {
mSnackbarHeight = dependency.getHeight();
}
int targetPadding = child.getMeasuredHeight();
int shadow = (int) ViewCompat.getElevation(child);
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) dependency.getLayoutParams();
layoutParams.bottomMargin = targetPadding - shadow;
child.bringToFront();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
child.getParent().requestLayout();
((View) child.getParent()).invalidate();
}
}
}
}
private class LollipopBottomNavWithSnackBarImpl implements BottomNavigationWithSnackbar {
@Override
public void updateSnackbar(CoordinatorLayout parent, View dependency, View child) {
if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) {
if (mSnackbarHeight == -1) {
mSnackbarHeight = dependency.getHeight();
}
int targetPadding = (mSnackbarHeight +
child.getMeasuredHeight());
dependency.setPadding(dependency.getPaddingLeft(),
dependency.getPaddingTop(), dependency.getPaddingRight(), targetPadding
);
}
}
}
}
import android.content.Context;
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.WindowInsetsCompat;
import android.util.AttributeSet;
import android.view.View;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by Nikola on 11/22/2015.
*/
public abstract class VerticalScrollingBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
private int mTotalDyUnconsumed = 0;
private int mTotalDy = 0;
@ScrollDirection
private int mOverScrollDirection = ScrollDirection.SCROLL_NONE;
@ScrollDirection
private int mScrollDirection = ScrollDirection.SCROLL_NONE;
public VerticalScrollingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerticalScrollingBehavior() {
super();
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({ScrollDirection.SCROLL_DIRECTION_UP, ScrollDirection.SCROLL_DIRECTION_DOWN})
public @interface ScrollDirection {
int SCROLL_DIRECTION_UP = 1;
int SCROLL_DIRECTION_DOWN = -1;
int SCROLL_NONE = 0;
}
/*
@return Overscroll direction: SCROLL_DIRECTION_UP, CROLL_DIRECTION_DOWN, SCROLL_NONE
*/
@ScrollDirection
public int getOverScrollDirection() {
return mOverScrollDirection;
}
/**
* @return Scroll direction: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN, SCROLL_NONE
*/
@ScrollDirection
public int getScrollDirection() {
return mScrollDirection;
}
/**
* @param coordinatorLayout
* @param child
* @param direction Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN
* @param currentOverScroll Unconsumed value, negative or positive based on the direction;
* @param totalOverScroll Cumulative value for current direction
*/
public abstract void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll);
/**
* @param scrollDirection Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN
*/
public abstract void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection);
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyUnconsumed > 0 && mTotalDyUnconsumed < 0) {
mTotalDyUnconsumed = 0;
mOverScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP;
} else if (dyUnconsumed < 0 && mTotalDyUnconsumed > 0) {
mTotalDyUnconsumed = 0;
mOverScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN;
}
mTotalDyUnconsumed += dyUnconsumed;
onNestedVerticalOverScroll(coordinatorLayout, child, mOverScrollDirection, dyConsumed, mTotalDyUnconsumed);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
if (dy > 0 && mTotalDy < 0) {
mTotalDy = 0;
mScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP;
} else if (dy < 0 && mTotalDy > 0) {
mTotalDy = 0;
mScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN;
}
mTotalDy += dy;
onDirectionNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, mScrollDirection);
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed) {
super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
mScrollDirection = velocityY > 0 ? ScrollDirection.SCROLL_DIRECTION_UP : ScrollDirection.SCROLL_DIRECTION_DOWN;
return onNestedDirectionFling(coordinatorLayout, child, target, velocityX, velocityY, mScrollDirection);
}
protected abstract boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection);
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
@Override
public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, V child, WindowInsetsCompat insets) {
return super.onApplyWindowInsets(coordinatorLayout, child, insets);
}
@Override
public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
return super.onSaveInstanceState(parent, child);
}
}
@masokaya
Copy link

thanks, this helped me

@AfeefAfasdel
Copy link

i will try , but u r using listview not (rycicalview)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment