防止在底部 Sheet 祖先视图中滚动

Prevent scrolls in Bottom Sheet ancestor view

我有一个 CoordinatorLayout 包含两个 NestedScrollView,一个有 ScrollingViewBehavior 另一个有 BottomSheetBehavior 并扮演 BottomSheet 角色,问题是当我拖动 BottomSheet 时,第一个 NestedScrollView 也会滚动:

我的期望:
当我滚动 BottomSheet 时,另一个 NestedScrollView 不应该滚动

我试过的:
我创建了一个自定义行为并覆盖 onInterceptTouchEvent,属于 BottomSheet 的事件返回 true 并在 BottomSheet 上调用 dispatchTouchEvent,它阻止了另一个 [=14] =] 滚动,但 BottomSheet 无法自行滚动,BottomSheet 子项上的点击事件不再起作用。

这是我的布局:

<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:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:ignore="UnusedAttribute">

<include
    layout="@layout/toolbar"/>

<android.support.v4.widget.NestedScrollView
    android:id="@+id/nested"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginBottom="?actionBarSize"
    android:background="#ff0"
    android:fillViewport="true"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <LinearLayout
        android:id="@+id/ll1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingTop="24dp">

        <Button
            android:id="@+id/button_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:background="@android:color/holo_green_dark"
            android:padding="16dp"
            android:text="Button 1"
            android:textColor="@android:color/white"/>

        <Button
            android:id="@+id/button_2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:background="@android:color/holo_blue_light"
            android:padding="16dp"
            android:text="Button 2"
            android:textColor="@android:color/white"/>

        <Button
            android:id="@+id/button_3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:background="@android:color/holo_red_dark"
            android:padding="16dp"
            android:text="Button 3"
            android:textColor="@android:color/white"/>

        <android.support.v4.widget.Space
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Bottom..."/>

    </LinearLayout>

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


<android.support.v4.widget.NestedScrollView
    android:id="@+id/bottom_sheet"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_orange_light"
    android:clipToPadding="false"
    android:elevation="4dp"
    android:fillViewport="true"
    app:behavior_peekHeight="60dp"
    app:layout_behavior=".Behavior">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:elevation="6dp"
            android:focusable="true"
            android:padding="16dp"
            android:text="@string/ipsum"
            android:textSize="16sp"/>

      <RecyclerView.../>

    </LinearLayout>


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

和我的 toolbar.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="320dp"
    android:fitsSystemWindows="true">

    <android.support.design.widget.CollapsingToolbarLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/lily_lake"
            app:layout_collapseMode="parallax"/>

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?actionBarSize"
            android:layout_gravity="bottom"
            app:layout_collapseMode="pin"/>


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

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

更新了更简单的解决方案。

你的问题的解决方案可以分为两部分:

  1. 如何在不滚动应用栏的情况下升高底部 sheet?
  2. 底部升起后如何降低sheet。

要在不影响应用栏的情况下提升底部 sheet,我们需要检测底部 sheet 何时负责滚动,并让应用栏拒绝嵌套滚动,这样它就不会收到随后的嵌套滚动事件。这是在 AppBarLayout.BehavioronStartNestedScroll 方法中完成的。 (请参阅下面 MyAppBarBehavior 的代码。)

我们现在可以向上拖动底部 sheet 就像一个独立的滑动面板。

现在我们已经把底部sheet往上拖了,我们能再往下拉吗?是的,没有。如果底部 sheet 滚动到其内容的顶部,那么我们可以将底部 sheet 向下拖动,如果我们触摸底部 sheet below应用栏。如果我们触摸底部 sheet 超过应用栏(当然,它在底部 sheet 后面),那么我们不能将底部 sheet 向下拖动。未经我们修改,该问题存在于底层代码中。我认为底部 sheet 覆盖应用栏是不寻常的。

为了演示此通知,在此视频中,当拖动底部 sheet 时,应用栏会在底部 sheet 后面打开。

要更改此行为,如果触摸底部 sheet,我们会将 AppBarLayout.BehavioronInterceptTouchEvent 覆盖为 return false。

这是新的 AppBarLayout.Behavior:

MyAppBarBehavior

class MyAppBarBehavior extends AppBarLayout.Behavior {
    private boolean mIsSheetTouched = false;

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                                       View directTargetChild, View target, int axes, int type) {
        // Set flag if the bottom sheet is responsible for the nested scroll.
        mIsSheetTouched = target.getId() == R.id.bottom_sheet;
        // Only consider starting a nested scroll if the bottom sheet is not touched; otherwise,
        // we will let the other views do the scrolling.
        return !mIsSheetTouched
            && super.onStartNestedScroll(coordinatorLayout, child, directTargetChild,
                                         target, axes, type);
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
        // Don't accept touch stream here if the bottom sheet is touched. This will permit the
        // bottom sheet to be dragged down without interaction with the appBar. Reset on cancel.
        if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
            mIsSheetTouched = false;
        }
        return !mIsSheetTouched && super.onInterceptTouchEvent(parent, child, ev);
    }
}

(我不应该用特定的id来识别底部sheet,但这只是一个演示。)

将自定义行为添加到应用栏:

<android.support.design.widget.AppBarLayout 
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="320dp"
    android:fitsSystemWindows="true"
    app:layout_behavior=".MyAppBarBehavior"
    app:expanded="true">

这是最终结果:

可能还有其他方法可以做到这一点。我可能会使用现有的滑动面板库,例如 AndroidSlidingUpPanel,而不是在嵌套滚动环境中放置底部 sheet,然后我们必须撤消环境的影响。