使用 setNestedScrollingEnabled(false) 时如何避免阻塞滚动本身?

How to avoid blocking of scrolling itself when using setNestedScrollingEnabled(false)?

背景

我们有一个相当复杂的布局,其中包含 CollapsingToolbarLayout,底部还有一个 RecyclerView。

在某些情况下,我们通过在 RecyclerView 上调用 setNestedScrollingEnabled(boolean) 来暂时禁用 CollapsingToolbarLayout 的 expanding/collapsing。

问题

这通常工作正常。

但是,在某些(很少见的)情况下,RecyclerView 上的慢速滚动会被半阻塞,这意味着它会在向下滚动时尝试向后滚动。就好像它有 2 个相互竞争的滚动(向上滚动和向下滚动):

触发代码如下:

res/layout/activity_scrolling.xml

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/nestedView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            android:id="@+id/disableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="disable"/>

        <Button
            android:id="@+id/enableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="enable"
            />
    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>

ScrollingActivity.java

public class ScrollingActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final RecyclerView nestedView = (RecyclerView) findViewById(R.id.nestedView);
        findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                nestedView.setNestedScrollingEnabled(false);
            }
        });
        findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                nestedView.setNestedScrollingEnabled(true);
            }
        });
        nestedView.setLayoutManager(new LinearLayoutManager(this));
        nestedView.setAdapter(new Adapter() {
            @Override
            public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
                        android.R.layout.simple_list_item_1,
                        parent,
                        false)) {
                };
            }

            @Override
            public void onBindViewHolder(final ViewHolder holder, final int position) {
                ((TextView) holder.itemView.findViewById(android.R.id.text1)).setText("item " + position);
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        });
    }

}

我试过的

一开始我以为是别的原因(我觉得是和DrawerLayout的奇怪组合),但是后来我找到了一个最小的例子来展示它,和我想的一样:都是因为设置嵌套滚动启用。

我试图在 Google 的网站 (here) 上报告此问题,希望如果这是一个真正的错误,它会得到修复。如果你想尝试一下,或者看问题的视频,去那里,因为我不能在这里全部上传(太大和太多文件)。

我也尝试按照其他帖子中的说明使用特殊标志(示例:, here, , here and ),但 none 有所帮助。事实上,他们每个人都有一个问题,无论是停留在展开模式,还是以与我不同的方式滚动。

问题

  1. 这是已知问题吗?为什么会这样?

  2. 有办法克服这个问题吗?

  3. 除了调用 setNestedScrollingEnabled 的这个函数之外,还有其他方法吗?没有任何滚动或锁定 CollapsingToolbarLayout 状态的问题?

正如@Moinkhan 指出的那样,您可以尝试像这样将 RecyclerView 和下一个元素包装在 NestedScrollView 中,这应该可以解决您在折叠工具栏布局时滚动的问题:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="fill_vertical"
        android:fillViewport="true"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/nestedView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

        </RelativeLayout>

    </android.support.v4.widget.NestedScrollView>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            android:id="@+id/disableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="disable"/>

        <Button
            android:id="@+id/enableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="enable"
            />
    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>

如果 recyclerview 的内容没有显示,您可以关注此线程来解决该问题 How to use RecyclerView inside NestedScrollView?

希望对您有所帮助。

实际上,您可能以错误的方式看待问题。

您唯一需要做的就是相应地设置 Toolbar 标志。你真的什么都没有,所以我会说你的布局应该简化为:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <android.support.design.widget.AppBarLayout
         android:id="@+id/app_bar"
         android:layout_width="match_parent"
         android:layout_height="@dimen/app_bar_height"
         android:fitsSystemWindows="true"
         android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:title="Title" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/nestedView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"            
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            android:id="@+id/disableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="disable"/>

        <Button
            android:id="@+id/enableNestedScrollingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="enable"
            />
    </LinearLayout>
</android.support.design.widget.CoordinatorLayout>

然后当您希望禁用折叠时,只需设置您的工具栏标志:

// To disable collapsing
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
toolbar.setLayoutParams(params);

并启用

// To enable collapsing
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL|AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
toolbar.setLayoutParams(params);

如果您正在更改而不是一直获取它,请保留对布局参数的引用。

如果你需要 CollapsingToolbarLayout get from 和 set LayoutParams 改为 View,以相同的方式更新标志,但现在添加 appBarLayout.setExpanded(true/false)

注意:使用setScrollFlags会清除所有以前的标志,因此在使用此方法时要小心并设置所有必需的标志。

在回收站视图中,滚动平滑

android:nestedScrollingEnabled="false" 

重叠工具栏中的cardView

 app:behavior_overlapTop = "24dp" 

Try this code for CollapsingToolbar:

  <android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/background"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"
                app:title="Title" />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>


    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:background="@android:color/transparent"
        app:behavior_overlapTop="@dimen/behavior_overlap_top"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:id="@+id/linearLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_min_padding"
                android:nestedScrollingEnabled="false"
                android:scrollbarSize="2dp"
                android:scrollbarStyle="outsideInset"
                android:scrollbarThumbVertical="@color/colorAccent"
                android:scrollbars="vertical" />

        </LinearLayout>

    </android.support.v4.widget.NestedScrollView>

</android.support.design.widget.CoordinatorLayout>

Screenshot

使用下面的代码,对我来说效果很好:

lockAppBarClosed();
ViewCompat.setNestedScrollingEnabled(recyclerView, false);   // to lock the CollapsingToolbarLayout

并实现以下方法:

private void setAppBarDragging(final boolean isEnabled) {
        CoordinatorLayout.LayoutParams params =
                (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
        AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
        behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
            @Override
            public boolean canDrag(AppBarLayout appBarLayout) {
                return isEnabled;
            }
        });
        params.setBehavior(behavior);
    }

    public void unlockAppBarOpen() {
        appBarLayout.setExpanded(true, false);
        appBarLayout.setActivated(true);
        setAppBarDragging(false);
    }

    public void lockAppBarClosed() {
        appBarLayout.setExpanded(false, false);
        appBarLayout.setActivated(false);
        setAppBarDragging(false);

    }

我认为此问题与折叠工具栏卡入到位(关闭或打开)并留下非零值的垂直偏移变量(mScrollOffset[1] in RecyclerView)有关随后使滚动条产生偏差——在一个方向上减慢或反转滚动条并在另一个方向上加速。如果启用嵌套滚动,此变量似乎仅在 NestedScrollingChildHelper 中设置。因此,一旦嵌套滚动被禁用,任何值 mScrollOffset[1] 都将保持不变。

要可靠地重现此问题,您可以使工具栏卡入到位,然后立即单击禁用。有关演示,请参阅 this video。我相信,问题的严重程度因 "snapping" 发生的程度而异。

如果我把工具栏拖到完全打开或关闭的位置,不让它"snap",那么我一直无法重现这个问题,mScrollOffset[1]设置为零我认为这是正确的价值。我还通过从布局中折叠工具栏的 layout_scrollFlags 中删除 snap 并将工具栏置于部分打开状态来重现该问题。

如果您想尝试一下,可以将您的演示应用程序置于调试模式并观察 RecyclerView#onTouchEventmScrollOffset[1] 的值。另请查看 NestedScrollingChildHelperdispatchNestedScrolldispatchNestedPreScroll 方法,了解仅在启用嵌套滚动时如何设置偏移量。

那么,如何解决这个问题? mScrollOffsetRecyclerView 是私有的,并且如何通过子class 任何东西来改变 mScrollOffset[1] 的值并不是很明显。那会留下 Reflection,但这可能不是您想要的。也许另一个 reader 知道如何处理这个问题或知道一些秘诀。 有什么事情我会重新发布

编辑: 我提供了一个新的 ScrollingActivity.java class 来解决这个问题。当按下禁用滚动按钮并且 AppBar 空闲时,它确实使用反射并应用补丁将 RecyclerViewmScrollOffset[1] 设置为零。我已经做了一些初步测试,它正在工作。 这是 gist.(请参阅下面的更新要点。)

第二次编辑:我能够让工具栏以有趣的方式捕捉并在没有补丁的情况下卡在中间,所以它看起来不像补丁导致那个特定的问题。通过在未修补的应用程序中足够快地向下滚动,我可以让工具栏从完全打开弹回到折叠状态。

我还再次查看了补丁的作用,我认为它会自行运行:变量是私有的,在关闭滚动后仅在一个地方引用。启用滚动后,变量总是在使用前重置。真正的答案是 Google 来解决这个问题。在他们这样做之前,我认为这可能是您可以使用此特定设计获得的最接近可接受的解决方法。 (我已经发布了一个 updated gist 解决了潜在的问题,通过快速点击使开关处于潜在的不合适状态。)

无论如何,潜在的问题已经被确定,并且您有一个可靠的方法来重现该问题,因此您可以更轻松地验证其他建议的解决方案。

希望对您有所帮助。

这是实现与 相同目标的另一种方法。虽然那个答案使用了反射,但这个答案没有,但推理是一样的。

为什么会这样?

问题是 RecyclerView 有时会对成员变量 mScrollOffset 使用过时的值。 mScrollOffsetRecyclerView 中仅在两个地方设置:dispatchNestedPreScrolldispatchNestedScroll。我们只关心 dispatchNestedPreScroll。此方法在处理 MotionEvent.ACTION_MOVE 事件时由 RecyclerView#onTouchEvent 调用。

以下内容来自 dispatchNestedPreScroll 的文档。

dispatchNestedPreScroll

boolean dispatchNestedPreScroll (int dx, int dy, int[] consumed, int[] offsetInWindow)

Dispatch one step of a nested scroll in progress before this view consumes any portion of it.

Nested pre-scroll events are to nested scroll events what touch intercept is to touch. dispatchNestedPreScroll offers an opportunity for the parent view in a nested scrolling operation to consume some or all of the scroll operation before the child view consumes it.

...

offsetInWindow int: Optional. If not null, on return this will contain the offset in local view coordinates of this view from before this operation to after it completes. View implementations may use this to adjust expected input coordinate tracking.

offsetInWindow 实际上是一个 int[2],第二个索引表示由于嵌套滚动而应用于 RecyclerView 的 y 位移。

RecyclerView#DispatchNestedPrescroll 解析为 NestedScrollingChildHelper.

中同名的方法

RecyclerView 调用 dispatchNestedPreScroll 时, mScrollOffset 用作 offsetInWindow 参数。因此,对 offsetInWindow 所做的任何更改都会直接更新 mScrollOffsetdispatchNestedPreScroll 更新 mScrollOffset 只要嵌套滚动有效。如果嵌套滚动无效,则 mScrollOffset 不会更新,并且 继续使用上次由 [=17 设置的值=].因此,当嵌套滚动关闭时,mScrollOffset 的值立即变得陈旧,但 RecyclerView 继续使用它。

dispatchNestedPreScroll 到 return 的 mScrollOffset[1] 的正确值是 input coordinate tracking 的调整量(见上文)。在 RecyclerView 中,以下行调整 y 触摸坐标:

mLastTouchY = y - mScrollOffset[1];

如果 mScrollOffset[1] 比方说是 -30(因为它已过时并且应该为零),那么 mLastTouchY 将偏移 +30 像素 (--30=+30)。这种计算错误的结果是,触摸发生在屏幕下方的位置比实际发生的要远。因此,缓慢的向下滚动实际上会向上滚动,而向上滚动会更快。 (如果向下滚动的速度足以克服此 30px 障碍,则向下滚动会发生,但速度会比应有的慢。)向上滚动会过快,因为应用程序认为 space 已被覆盖.

mScrollOffset 将继续作为陈旧变量,直到打开嵌套滚动并且 dispatchNestedPreScroll 再次报告 mScrollOffset 中的正确值。

接近

由于 mScrollOffset[1] 在某些情况下具有陈旧值,目标是在这些情况下将其设置为正确的值。当没有发生嵌套滚动时,即当 AppBar 展开或折叠时,此值应为零。不幸的是,mScrollOffsetRecyclerView 本地的,没有 setter。为了在不借助反射的情况下访问 mScrollOffset,创建了一个覆盖 dispatchNestedPreScroll 的自定义 RecyclerView。第四个参数是offsetInWindow,这是我们需要改变的变量。

只要为 RecyclerView 禁用嵌套滚动,就会出现陈旧的 mScrollOffset。我们将施加的另一个条件是 AppBar 必须空闲,因此我们可以安全地说 mScrollOffset[1] 应该为零。这不是问题,因为 CollapsingToolbarLayout 在滚动标志中指定了 snap

在示例应用中,ScrollingActivity 已被修改为记录 AppBar 展开和关闭的时间。还创建了一个回调 (clampPrescrollOffsetListener),当满足我们的两个条件时,它将 return true。我们覆盖的 dispatchNestedPreScroll 将调用此回调并在 true 响应中将 mScrollOffset[1] 钳位为零。

下面显示了 ScrollingActivity 的更新源文件和自定义 RecyclerView - MyRecyclerView。 必须更改 XML 布局文件以反映自定义 MyRecyclerView.

滚动活动

public class ScrollingActivity extends AppCompatActivity
        implements MyRecyclerView.OnClampPrescrollOffsetListener {

    private CollapsingToolbarLayout mCollapsingToolbarLayout;
    private AppBarLayout mAppBarLayout;
    private MyRecyclerView mNestedView;
    // This variable will be true when the app bar is completely open or completely collapsed.
    private boolean mAppBarIdle = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mNestedView = (MyRecyclerView) findViewById(R.id.nestedView);
        mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar);
        mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);

        // Set the listener for the patch code.
        mNestedView.setOnClampPrescrollOffsetListener(this);

        // Listener to determine when the app bar is collapsed or fully open (idle).
        mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                mAppBarIdle = verticalOffset == 0
                        || verticalOffset <= appBarLayout.getTotalScrollRange();
            }
        });
        findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // If the AppBar is fully expanded or fully collapsed (idle), then disable
                // expansion and apply the patch; otherwise, set a flag to disable the expansion
                // and apply the patch when the AppBar is idle.
                setExpandEnabled(false);

            }
        });
        findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                setExpandEnabled(true);
            }
        });
        mNestedView.setLayoutManager(new LinearLayoutManager(this));
        mNestedView.setAdapter(new Adapter() {
            @Override
            public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
                        android.R.layout.simple_list_item_1,
                        parent,
                        false)) {
                };
            }

            @Override
            public void onBindViewHolder(final ViewHolder holder, final int position) {
                ((TextView) holder.itemView.findViewById(android.R.id.text1)).setText("item " + position);
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        });
    }

    private void setExpandEnabled(boolean enabled) {
        mNestedView.setNestedScrollingEnabled(enabled);
    }

    // Return "true" when the app bar is idle and nested scrolling is disabled. This is a signal
    // to the custom RecyclerView to clamp the y prescroll offset to zero.
    @Override
    public boolean clampPrescrollOffsetListener() {
        return mAppBarIdle && !mNestedView.isNestedScrollingEnabled();
    }

    private static final String TAG = "ScrollingActivity";
}

MyRecyclerView

public class MyRecyclerView extends RecyclerView {
    private OnClampPrescrollOffsetListener mPatchListener;

    public MyRecyclerView(Context context) {
        super(context);
    }

    public MyRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    // Just a call to super plus code to force offsetInWindow[1] to zero if the patchlistener
    // instructs it.
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        boolean returnValue;
        int currentOffset;
        returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
        currentOffset = offsetInWindow[1];
        Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset);
        if (mPatchListener.clampPrescrollOffsetListener() && offsetInWindow[1] != 0) {
            Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset + " -> 0");
            offsetInWindow[1] = 0;
        }
        return returnValue;
    }

    public void setOnClampPrescrollOffsetListener(OnClampPrescrollOffsetListener patchListener) {
        mPatchListener = patchListener;
    }

    public interface OnClampPrescrollOffsetListener {
        boolean clampPrescrollOffsetListener();
    }

    private static final String TAG = "MyRecyclerView";
}

我不得不解决一个类似的问题,并在 AppBarLayout 上使用自定义行为解决了这个问题。一切都很好。 通过覆盖自定义行为中的 onStartNestedScroll,可以阻止折叠工具栏布局展开或折叠,同时在我的例子中保持滚动视图 (NestedScrollView),按预期工作。我解释的很详细here,希望对你有帮助

private class AppBarLayoutBehavior : AppBarLayout.Behavior() {
    var canDrag = true
    var acceptsNestedScroll = true

    init {
        setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
            override fun canDrag(appBarLayout: AppBarLayout): Boolean {
                // Allow/Do not allow dragging down/up to expand/collapse the layout
                return canDrag
            }
        })
    }

    override fun onStartNestedScroll(parent: CoordinatorLayout,
                                     child: AppBarLayout,
                                     directTargetChild: View,
                                     target: View,
                                     nestedScrollAxes: Int,
                                     type: Int): Boolean {
        // Refuse/Accept any nested scroll event
        return acceptsNestedScroll
    }}

我想提出一个不错的选择,主要基于 :

AppBarLayoutEx.kt

class AppBarLayoutEx : AppBarLayout {
    private var isAppBarExpanded = true
    private val behavior = AppBarLayoutBehavior()
    private var onStateChangedListener: (Boolean) -> Unit = {}
    var enableExpandAndCollapseByDraggingToolbar: Boolean
        get() = behavior.canDrag
        set(value) {
            behavior.canDrag = value
        }

    var enableExpandAndCollapseByDraggingContent: Boolean
        get() = behavior.acceptsNestedScroll
        set(value) {
            behavior.acceptsNestedScroll = value
        }

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    init {
        addOnOffsetChangedListener(
                AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
                    isAppBarExpanded = verticalOffset == 0
                    onStateChangedListener(isAppBarExpanded)
                })
    }

    override fun setLayoutParams(params: ViewGroup.LayoutParams?) {
        super.setLayoutParams(params)
        (params as CoordinatorLayout.LayoutParams).behavior = behavior
    }

    fun toggleExpandedState() {
        setExpanded(!isAppBarExpanded, true)
    }

    fun setOnExpandAndCollapseListener(onStateChangedListener: (Boolean) -> Unit) {
        this.onStateChangedListener = onStateChangedListener
    }

    private class AppBarLayoutBehavior : AppBarLayout.Behavior() {
        var canDrag = true
        var acceptsNestedScroll = true

        init {
            setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
                override fun canDrag(appBarLayout: AppBarLayout) = canDrag
            })
        }

        override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View,
                                         target: View, nestedScrollAxes: Int, type: Int) = acceptsNestedScroll
    }
}

用法:除了在布局 XML 文件中使用它之外,您还可以 disable/enable 扩展它使用:

appBarLayout.enableExpandAndCollapseByDraggingToolbar = true/false

appBarLayout.enableExpandAndCollapseByDraggingContent = true/false