0

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?!?

Andrew
  • 893
  • 12
  • 28

1 Answers1

1

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);
            }
        });
cgb_pandey
  • 985
  • 1
  • 11
  • 18
  • What about a scenario where I want the `AppBarLayout` to be collapsed, but also disabled? Currently, if I change the flags to `NO_SCROLL` when the app bar is already collapsed, it instantly expands fully without animation, even though I want it to be collapsed. – Andrew Jul 07 '20 at 05:41
  • So, even if `AppBarLayout` is collapsed, the `EditText` is still visible ? Can I see your UI once when `AppBarLayout` is collapsed ? – cgb_pandey Jul 07 '20 at 06:22
  • Sorry that wasn't clear. When the `AppBarLayout` is collapsed the `EditText` should be hidden. All that remains visible from a UI perspective is the `RecyclerView`. I.e. if I call `appBarLayout.setExpanded(false)` the `EditText` animates and collapses, and can no longer be seen. But, once it is collapsed, if I set the flags to `NO_SCROLL` as you mentioned, it's not possible for the `AppBarLayout` to remain collapsed. It instantly goes back to it's full expanded height when I set the flags. – Andrew Jul 07 '20 at 15:30
  • I only mentioned to use `NO_SCROLL` flag to prevent the `AppBarLayout` from being `collapsed` when `searchField` has focus and that's what you have asked in the question I think. If my answer actually answers to your that problem then you should accept the answer and create a new thread for this problem according to SO rules. Sorry if I sound rude. – cgb_pandey Jul 08 '20 at 03:20
  • Also, if `AppBarLayout` is `collapsed`, and you wouldn't want `AppBarLayout` to react to scroll, then your `searchField` is never to be seen. So, user wouldn't be able to filter the results. I think that would made a bad UX. Correct me if I'm wrong. – cgb_pandey Jul 08 '20 at 03:28
  • While I didn't mention it in the question, I was hoping that what worked for locking an `AppBarLayout` open would also work to keep one locked closed. Keeping it locked closed is for a different use case in the app (as you mentioned, doesn't make sense for the search bar) but I thought it would be easier to ask within this context. – Andrew Jul 08 '20 at 16:01
  • In reality, I actually have nested `CoordinatorLayouts`. The parent needs to be locked closed (has a header to be hidden) and the child needs to be locked open (the search bar). It's a fairly complex view, but I can't confidently say that your solution fixes my problem yet because the bug still appears to be showing up. That's likely happening in the parent `CoordinatorLayout` where I can't use the scroll flags, but I haven't had the time to strip away the parent coordinator to isolate the coordinator with the search bar, thus I haven't marked the question as answered. I will once I have time. – Andrew Jul 08 '20 at 16:06
  • Oh!, I didn't know you had a nested `CoordinatorLayout`. I tried in a single `CoordinatorLayout` and written the answer based on that. But, it is bad practice to use nested layouts in terms of performance. I want to recommend you to try and reduce one `CoordinatorLayout`. – cgb_pandey Jul 09 '20 at 05:29