Search code examples
androidxmlkotlinmaterial-designandroid-coordinatorlayout

How do I achieve on scroll behavior where both the toolbar and bottom navigation should hide?Only one of them is working


I am trying to achieve the coordinator layout behaviour where on scrolling the recycler view can hide both the toolbar and bottom navigation view. So far I have achieved one success i.e bottom navigation bottom bar does hide but with one caveat that it remains active even when the keyboard is on(how do I fix that too?) My main concern here is how do I achieve the same feature of bottom navigation view of hiding into the toolbar?

I have included the custom toolbar in Appbar layout, but I have tried to add the Toolbar layout tag too in the AppBar nothing works, It just remains the same. And for the bottomnavigation jumping up on top I don't know what to do? till now I have added snap scroll flags on the bottomnavigation view to stop this behaviour and also snap flag didn't work, I think so, coz it remains in halfway position while going up on search tap. Got this BottomNavigationBehavior from the wonderful article.

reference

video showing behavior

image for snap behavior

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
>
    <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="?actionBarSize"
            app:elevation="0dp"
            android:background="@android:color/transparent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
    >
        <include
                app:layout_scrollFlags="scroll|enterAlways|snap"
                layout="@layout/browser_search_tap_tb"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/browser_tb"

        />
    </com.google.android.material.appbar.AppBarLayout>
    <!--Scrolling effect for the bottom nav menu-->
    <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:listitem="@layout/rv_test_items"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            android:id="@+id/rv_test"
    />
    <!--Bottom navigation view for the Selection of the Tabs and Items in Menu-->
    <com.google.android.material.bottomnavigation.BottomNavigationView
            app:layout_scrollFlags="scroll|enterAlways|snap"
            android:id="@+id/browser_bottom_nav_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:background="#ffff"
            app:layout_behavior="com.example.android.browserui.BottomNavigationBehavior"
            app:labelVisibilityMode="unlabeled"
            app:menu="@menu/bottom_nav_menu"
    />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

browser_search_tap_tb.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="?actionBarSize"
        android:background="@android:color/transparent"
        app:layout_scrollFlags="scroll|enterAlways"
        app:popupTheme="@style/ThemeOverlay.AppCompat"
        app:contentInsetStart="8dp"
        app:contentInsetEnd="8dp"
>
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        <EditText
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:hint="Search or type new address"
                android:padding="8dp"
                android:paddingEnd="12dp"
                android:paddingStart="12dp"
                android:drawableEnd="@drawable/ic_mic"
                android:inputType="textWebEditText"
                android:background="@drawable/rounded_et_search"
                app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent" android:id="@+id/et_search_bar_tap"
                app:layout_constraintEnd_toEndOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.appcompat.widget.Toolbar>

BottomNavigationBehavior.kt

class BottomNavigationBehavior<V : View>(context: Context, attrs: AttributeSet) :
    CoordinatorLayout.Behavior<V>(context, attrs) {

    private var lastStartedType: Int = 0

    private var offsetAnimator: ValueAnimator? = null

    var isSnappingEnabled = false

    override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
        if (dependency is Snackbar.SnackbarLayout) {
            updateSnackbar(child, dependency)
        }
        return super.layoutDependsOn(parent, child, dependency)
    }

    override fun onStartNestedScroll(
        coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View, target: View, axes: Int, type: Int
    ): Boolean {
        if (axes != ViewCompat.SCROLL_AXIS_VERTICAL)
            return false

        lastStartedType = type

        offsetAnimator?.cancel()

        return true
    }

    override fun onNestedPreScroll(
        coordinatorLayout: CoordinatorLayout, child: V, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int
    ) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
        child.translationY = max(0f, min(child.height.toFloat(), child.translationY + dy))
    }

    override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {
        if (!isSnappingEnabled)
            return

        // add snap behaviour
        // Logic here borrowed from AppBarLayout onStopNestedScroll code
        if (lastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH) {
            // find nearest seam
            val currTranslation = child.translationY
            val childHalfHeight = child.height * 0.5f

            // translate down
            if (currTranslation >= childHalfHeight) {
                animateBarVisibility(child, isVisible = false)
            }
            // translate up
            else {
                animateBarVisibility(child, isVisible = true)
            }
        }
    }

    private fun animateBarVisibility(child: View, isVisible: Boolean) {
        if (offsetAnimator == null) {
            offsetAnimator = ValueAnimator().apply {
                interpolator = DecelerateInterpolator()
                duration = 150L
            }

            offsetAnimator?.addUpdateListener {
                child.translationY = it.animatedValue as Float
            }
        } else {
            offsetAnimator?.cancel()
        }

        val targetTranslation = if (isVisible) 0f else child.height.toFloat()
        offsetAnimator?.setFloatValues(child.translationY, targetTranslation)
        offsetAnimator?.start()
    }

    private fun updateSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) {
        if (snackbarLayout.layoutParams is CoordinatorLayout.LayoutParams) {
            val params = snackbarLayout.layoutParams as CoordinatorLayout.LayoutParams

            params.anchorId = child.id
            params.anchorGravity = Gravity.TOP
            params.gravity = Gravity.TOP
            snackbarLayout.layoutParams = params
        }
    }
}

Solution

  • So Finally Resolved My issue after getting my head around for 4 days here are some changes which I did and solved the issues : activity_main.xml

    <com.google.android.material.appbar.AppBarLayout
                android:id="@+id/appbar"
                android:layout_width="match_parent"
                android:layout_height="?actionBarSize"
                app:elevation="0dp"
                android:background="@android:color/transparent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior"<!--Removed this line-->
        >
    
    
    <!--Bottom navigation view for the Selection of the Tabs and Items in Menu-->
        <com.google.android.material.bottomnavigation.BottomNavigationView
                app:layout_scrollFlags="scroll|enterAlways|snap"<!--Removed this line-->
                android:id="@+id/browser_bottom_nav_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:background="#ffff"
                app:layout_behavior="com.example.android.browserui.BottomNavigationBehavior"
                app:labelVisibilityMode="unlabeled"
                app:menu="@menu/bottom_nav_menu"
        />
    
    

    Here in activity_main.xml, I removed the layout_behavior because the appbar itself is invoking the scroll behavior for other layout items, it acts as a parent.

    app:layout_behavior="@string/appbar_scrolling_view_behavior"

    Also removed scrollflags from the bottom navigation view, AS I was implementing this behavior from the Class BottomNavigationBehavior.kt, You find the implementation below

    app:layout_scrollFlags="scroll|enterAlways|snap"

    browser_search_tap_tb.xml

    
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.appcompat.widget.Toolbar
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="?actionBarSize"
            android:background="@android:color/transparent"
            app:layout_scrollFlags="scroll|enterAlways" <!--Removed this line-->
            app:popupTheme="@style/ThemeOverlay.AppCompat"
            app:contentInsetStart="8dp"
            app:contentInsetEnd="8dp"
    >
    

    Here in browser_search_tb, I removed the following line because it was overriding the Scrollflags in co-ordinator layout so removed it and it worked flawlessly

    app:layout_scrollFlags="scroll|enterAlways"

    BottomNavigationBehavior.kt

    override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {
            if (!isSnappingEnabled)
               return // removed this line
    {
            // add snap behaviour
            // Logic here borrowed from AppBarLayout onStopNestedScroll code
            if (lastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH) {
                // find nearest seam
                val currTranslation = child.translationY
                val childHalfHeight = child.height * 0.5f
    
                // translate down
                if (currTranslation >= childHalfHeight) {
                    animateBarVisibility(child, isVisible = false)
                }
                // translate up
                else {
                    animateBarVisibility(child, isVisible = true)
                }
            }
    }
    }
    

    Here I removed the

    return

    and added braces{} to the if statement and snap feature did work properly

    Hope this answer will help you and will cut down your debugging time. The above reference is one of the best article you can find for scrolling behavior on the internet it simple and smooth to grasp