Reputation: 933
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
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