aurelhubert / ahbottomnavigation

A library to reproduce the behavior of the Bottom Navigation guidelines from Material Design.
3.84k stars 684 forks source link

navigation will hide unexpectedly when RecyclerView is inside a fragment with SwipeLayout #81

Closed kirsysuv closed 8 years ago

kirsysuv commented 8 years ago

I have a RecyclerView inside SwipeLayout in a fragment. The navigation will always perform a hide animation when swipe up even there is not enough items in RecyclerView to scroll. The layout:

    <android.support.design.widget.CoordinatorLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="?android:attr/actionBarSize">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            style="@style/ActionBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:popupTheme="@style/Theme.MyTheme.MenuOverlay"
            app:title="@string/Mytitle"
            app:titleTextAppearance="@style/ToolbarTextAppearance" />
    </android.support.design.widget.AppBarLayout>

    <FrameLayout
        android:id="@+id/content_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <com.aurelhubert.ahbottomnavigation.AHBottomNavigation
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />

</android.support.design.widget.CoordinatorLayout>
kirsysuv commented 8 years ago

Confirmed in a simpler layout without fragment. The layout is showed as below. The RecyclerView only have 1 item and the height is less than half of screen. When swipe upwards, the bottomnavigation will hide.

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.ky.swipelayouttest.MainActivity">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </android.support.v4.widget.SwipeRefreshLayout>

    <com.aurelhubert.ahbottomnavigation.AHBottomNavigation
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />
</android.support.design.widget.CoordinatorLayout>
aurelhubert commented 8 years ago

@kirsting It seems that the problem comes from the AHBottomNavigationBehavior class (or more precisely VerticalScrollingBehavior). I'm not sure I can do something from this class. What do you think?

kirsysuv commented 8 years ago

I spend some time on it yesterday. I think the point is to use the dyConsumed to identify the scroll. dyConsumed will not be 0 when there is scrolling happening in other view. When dyConsumed is 0, which means there is no real scroll and the hide/show animation should not start. I tried to only response to onNestedScroll (this method give us dyConsumed) and it seems works. Below is the modified behavior which may not be the best implementation.

public class BottomNavigationBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {

        private static final Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator();
        private static final int ANIM_DURATION = 300;
        private static final int SCROLL_DIRECTION_DOWN = -1;
        private static final int SCROLL_DIRECTION_UP = 1;

        private int mTabLayoutId;
        private boolean hidden = false;
        private ViewPropertyAnimatorCompat translationAnimator;
        private ObjectAnimator translationObjectAnimator;
        private TabLayout mTabLayout;
        private Snackbar.SnackbarLayout snackbarLayout;
        private FloatingActionButton floatingActionButton;
        private int mSnackbarHeight = -1;
        private boolean fabBottomMarginInitialized = false;
        private float targetOffset = 0, fabTargetOffset = 0, fabDefaultBottomMargin = 0, snackBarY = 0;
        private boolean behaviorTranslationEnabled = true;

        public BottomNavigationBehavior() {
            super();
        }

        @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);
            }
            return layoutChild;
        }

        private TabLayout findTabLayout(View child) {
            if (mTabLayoutId == 0) return null;
            return (TabLayout) child.findViewById(mTabLayoutId);
        }

        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
            return super.onDependentViewChanged(parent, child, dependency);
        }

        @Override
        public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
            super.onDependentViewRemoved(parent, child, dependency);
        }

        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
            updateSnackbar(child, dependency);
            updateFloatingActionButton(dependency);
            return super.layoutDependsOn(parent, child, dependency);
        }

//ViewCompat for Compatibility
        @Override
        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
            return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
        }

//handleDirection based on dyConsumed
        @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 (dyConsumed < 0) {
                handleDirection(child, SCROLL_DIRECTION_DOWN);
            } else if (dyConsumed > 0) {
                handleDirection(child, SCROLL_DIRECTION_UP);
            }
        }

        private void handleDirection(V child, int scrollDirection) {
            if (scrollDirection == SCROLL_DIRECTION_DOWN && hidden) {
                hidden = false;
                animateOffset(child, 0, false, true);
            } else if (scrollDirection == SCROLL_DIRECTION_UP && !hidden) {
                hidden = true;
                animateOffset(child, child.getHeight(), false, true);
            }
        }
...the rest is not modified
aurelhubert commented 8 years ago

@kirsting Perfect, thanks, I'll add the fix on the next release (1.2.3).

aurelhubert commented 8 years ago

Fixed in version 1.2.3