在 CoordinatorLayout 中禁用 AppBarLayout 时 RecyclerView 滚动出现故障?
RecyclerView scrolling glitches when disabling AppBarLayout in a CoordinatorLayout?
背景
我有一个基于 CoordinatorLayout
的视图,它有一个 AppbarLayout
和 RecyclerView
作为直接子项。 AppBarLayout
包含一个搜索栏,当您向上滚动 RecyclerView
时该搜索栏会折叠。此视图的 XML 是:
<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>
当您点击搜索栏中的 EditText
视图时,将启用我所说的“搜索模式”。搜索模式所做的只是在滚动 RecyclerView
时禁用 AppBarLayout
折叠。然后用户可以在搜索栏中输入内容——它会过滤 RecyclerView
中的项目——然后他们可以在搜索栏不折叠的情况下滚动列表。我连接到 EditText
onFocus
事件来执行此操作:
searchField.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
// When user taps the search bar, enable search mode
enableSearchMode()
}
}
而 enableSearchMode
代码是:
private fun enableSearchMode() {
...
itemRecyclerView.isNestedScrollingEnabled = false
...
}
问题
这个设置似乎工作得很好......大多数时候。随机 - 可能是 1% 的时间 - 当您触摸 EditText
以启用搜索模式时,出现问题并且我无法再有效地滚动 RecyclerView
。就好像它被卡在了列表的顶部。如果您尝试向列表底部滚动,滚动会四处晃动,并且通常会跳回到列表顶部。一旦禁用搜索模式,问题就会消失。
// Disable search mode
itemRecyclerView.isNestedScrollingEnabled = true
尽管进行了大量测试,但我无法始终如一地重现该问题或确定导致该问题的操作进展。它只是随机发生的,就好像 CoordinatorLayout
.
中发生了某种竞争条件
我在我的应用程序中删除了太多代码来隔离问题,我确信当我将 isNestedScrollingEnabled
设置为 false 时问题恰好发生。也就是说,我还尝试了一种替代方法来在 RecyclerView
滚动时禁用 AppBarLayout
移动,即覆盖 AppBarLayout
的行为,如 here 所述。奇怪的是,这会导致同样的问题。如果我不通过这种方式或通过设置 isNestedScrollingEnabled
false 禁用 AppBarLayout
,则问题永远不会出现。
这里发生了什么?!?
What is happening here?!?
将 isNestedScrollingEnabled
设置为 false,实际上会中断 itemRecyclerView
之间的通信,因为 滚动 child 和 AppBarLayout
因为 它是 parent。正常行为是 itemRecyclerView
通知它 parent AppBarLayout
它正在滚动进度,为此 parent 应该通过 计算它的折叠高度给定任何滚动进度和所有其他内容.
我在某处发现将 isNestedScrollingEnabled
设置为 false 会导致 RecyclerView
到 不回收 其视图。我不能确切地说这是不是真的,但如果是的话,我认为这是导致故障的原因。
我想提出的解决方案是以编程方式将 scroll_flags
更改为 NO_SCROLL
,这样 AppBarLayout
就不会 react/scroll 滚动其 child滚动视图。
虽然它在 java 中,但以下代码片段应该对您有所帮助。
// 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);
}
});
背景
我有一个基于 CoordinatorLayout
的视图,它有一个 AppbarLayout
和 RecyclerView
作为直接子项。 AppBarLayout
包含一个搜索栏,当您向上滚动 RecyclerView
时该搜索栏会折叠。此视图的 XML 是:
<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>
当您点击搜索栏中的 EditText
视图时,将启用我所说的“搜索模式”。搜索模式所做的只是在滚动 RecyclerView
时禁用 AppBarLayout
折叠。然后用户可以在搜索栏中输入内容——它会过滤 RecyclerView
中的项目——然后他们可以在搜索栏不折叠的情况下滚动列表。我连接到 EditText
onFocus
事件来执行此操作:
searchField.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
// When user taps the search bar, enable search mode
enableSearchMode()
}
}
而 enableSearchMode
代码是:
private fun enableSearchMode() {
...
itemRecyclerView.isNestedScrollingEnabled = false
...
}
问题
这个设置似乎工作得很好......大多数时候。随机 - 可能是 1% 的时间 - 当您触摸 EditText
以启用搜索模式时,出现问题并且我无法再有效地滚动 RecyclerView
。就好像它被卡在了列表的顶部。如果您尝试向列表底部滚动,滚动会四处晃动,并且通常会跳回到列表顶部。一旦禁用搜索模式,问题就会消失。
// Disable search mode
itemRecyclerView.isNestedScrollingEnabled = true
尽管进行了大量测试,但我无法始终如一地重现该问题或确定导致该问题的操作进展。它只是随机发生的,就好像 CoordinatorLayout
.
我在我的应用程序中删除了太多代码来隔离问题,我确信当我将 isNestedScrollingEnabled
设置为 false 时问题恰好发生。也就是说,我还尝试了一种替代方法来在 RecyclerView
滚动时禁用 AppBarLayout
移动,即覆盖 AppBarLayout
的行为,如 here 所述。奇怪的是,这会导致同样的问题。如果我不通过这种方式或通过设置 isNestedScrollingEnabled
false 禁用 AppBarLayout
,则问题永远不会出现。
这里发生了什么?!?
What is happening here?!?
将 isNestedScrollingEnabled
设置为 false,实际上会中断 itemRecyclerView
之间的通信,因为 滚动 child 和 AppBarLayout
因为 它是 parent。正常行为是 itemRecyclerView
通知它 parent AppBarLayout
它正在滚动进度,为此 parent 应该通过 计算它的折叠高度给定任何滚动进度和所有其他内容.
我在某处发现将 isNestedScrollingEnabled
设置为 false 会导致 RecyclerView
到 不回收 其视图。我不能确切地说这是不是真的,但如果是的话,我认为这是导致故障的原因。
我想提出的解决方案是以编程方式将 scroll_flags
更改为 NO_SCROLL
,这样 AppBarLayout
就不会 react/scroll 滚动其 child滚动视图。
虽然它在 java 中,但以下代码片段应该对您有所帮助。
// 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);
}
});