Andrew
Andrew

Reputation: 933

RecyclerView scrolling glitches when disabling AppBarLayout in a CoordinatorLayout?

Background

I have a CoordinatorLayout based view, which has an AppbarLayout and RecyclerView as direct children. The AppBarLayout houses a search bar that collapses when you scroll the RecyclerView up. The XML for this view is:

<androidx.coordinatorlayout.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/search_coordinator"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/search_app_bar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent"
            app:elevation="0dp">

        <!-- Search bar -->
        <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/search_constraint"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:focusableInTouchMode="true"
                android:focusable="true"
                app:layout_scrollFlags="scroll|enterAlwaysCollapsed|snap">

            <!-- Abbreviated -->
            <EditText .../>

            <!-- Abbreviated -->
            <ImageView .../>

        </androidx.constraintlayout.widget.ConstraintLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <!-- List -->
    <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/scroll_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

When you tap on the EditText view in the search bar, that enables what I call "Search Mode". All that search mode does is disable the AppBarLayout from collapsing when scrolling the RecyclerView. The user can then type into the search bar - which filters the items in the RecyclerView - and then they can scroll the list without the search bar collapsing. I hook into the EditText onFocus events to perform this:

searchField.setOnFocusChangeListener { _, hasFocus ->
    if (hasFocus) {
        // When user taps the search bar, enable search mode
        enableSearchMode()
    }
}

And the enableSearchMode code is:

private fun enableSearchMode() {
    ...

    itemRecyclerView.isNestedScrollingEnabled = false

    ...
}

Problem

This setup seems to work perfectly... most of the time. Randomly - maybe 1% of the time - when you touch the EditText to enable search mode, something goes wrong and I'm not able to effectively scroll the RecyclerView anymore. It is like it is stuck at the top of the list. If you try to scroll towards the bottom of the list, the scrolling jerks around, and generally jumps back up to the top of the list. As soon as the search mode is disabled, the problem goes away.

// Disable search mode
itemRecyclerView.isNestedScrollingEnabled = true

Despite an enormous amount of testing, I have not been able to consistently reproduce the issue or determine what progression of actions leads to it. It just randomly happens, as if there is some sort of race condition going on within the CoordinatorLayout.

I have stripped away so much code in my app to isolate the issue that I am confident the issue occurs precisely when I set isNestedScrollingEnabled to false. That said, I have also tried an alternative to disabling the AppBarLayout from moving when the RecyclerView is scrolled, which is to override the behavior of the AppBarLayout as described here. Oddly enough, this leads to the same problem. If I don't disable the AppBarLayout either through this means or via setting isNestedScrollingEnabled false, the issue never appears.

What is happening here?!?

Upvotes: 0

Views: 831

Answers (1)

cgb_pandey
cgb_pandey

Reputation: 1025

What is happening here?!?

Setting isNestedScrollingEnabled to false, in fact, breaks the communication between your itemRecyclerView as scrolling child and AppBarLayout as it's parent. The normal behaviour is itemRecyclerView notifies it's parent AppBarLayout it's scrolling progress for which the parent is supposed to react to it by calculating it's collapsed height given any scrolling progress and all other stuffs.
I found somewhere that setting isNestedScrollingEnabled to false would cause the RecyclerView to not recycle its views. I can't say exactly if it's true but if it is then, I think it's cause for that glitch.

The solution that I would like to propose is to change the scroll_flags programmatically to NO_SCROLL so that AppBarLayout wouldn't react/scroll in scrolling of its child scrolling view.
Although, it's in java but following code snippet should help you.

            // get a reference for your constraint layout
            ConstraintLayout constraintLayout = findViewById(R.id.search_constraint);
            // get the layout params object to change the scroll flags programmatically
            final AppBarLayout.LayoutParams layoutParams = (AppBarLayout.LayoutParams) constraintLayout.getLayoutParams();
    
            // flags variable to switch between
            final int noScrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL;
            final int defaultScrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL 
                | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED
                | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP;

        // now we will set appropriate scroll flags according to the focus
        searchField.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                layoutParams.setScrollFlags(hasFocus ? noScrollFlags : defaultScrollFlags);
            }
        });

Upvotes: 1

Related Questions