防止在底部 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>
更新了更简单的解决方案。
你的问题的解决方案可以分为两部分:
- 如何在不滚动应用栏的情况下升高底部 sheet?
- 底部升起后如何降低sheet。
要在不影响应用栏的情况下提升底部 sheet,我们需要检测底部 sheet 何时负责滚动,并让应用栏拒绝嵌套滚动,这样它就不会收到随后的嵌套滚动事件。这是在 AppBarLayout.Behavior
的 onStartNestedScroll
方法中完成的。 (请参阅下面 MyAppBarBehavior
的代码。)
我们现在可以向上拖动底部 sheet 就像一个独立的滑动面板。
现在我们已经把底部sheet往上拖了,我们能再往下拉吗?是的,没有。如果底部 sheet 滚动到其内容的顶部,那么我们可以将底部 sheet 向下拖动,如果我们触摸底部 sheet below应用栏。如果我们触摸底部 sheet 超过应用栏(当然,它在底部 sheet 后面),那么我们不能将底部 sheet 向下拖动。未经我们修改,该问题存在于底层代码中。我认为底部 sheet 覆盖应用栏是不寻常的。
为了演示此通知,在此视频中,当拖动底部 sheet 时,应用栏会在底部 sheet 后面打开。
要更改此行为,如果触摸底部 sheet,我们会将 AppBarLayout.Behavior
的 onInterceptTouchEvent
覆盖为 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,然后我们必须撤消环境的影响。
我有一个 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>
更新了更简单的解决方案。
你的问题的解决方案可以分为两部分:
- 如何在不滚动应用栏的情况下升高底部 sheet?
- 底部升起后如何降低sheet。
要在不影响应用栏的情况下提升底部 sheet,我们需要检测底部 sheet 何时负责滚动,并让应用栏拒绝嵌套滚动,这样它就不会收到随后的嵌套滚动事件。这是在 AppBarLayout.Behavior
的 onStartNestedScroll
方法中完成的。 (请参阅下面 MyAppBarBehavior
的代码。)
我们现在可以向上拖动底部 sheet 就像一个独立的滑动面板。
现在我们已经把底部sheet往上拖了,我们能再往下拉吗?是的,没有。如果底部 sheet 滚动到其内容的顶部,那么我们可以将底部 sheet 向下拖动,如果我们触摸底部 sheet below应用栏。如果我们触摸底部 sheet 超过应用栏(当然,它在底部 sheet 后面),那么我们不能将底部 sheet 向下拖动。未经我们修改,该问题存在于底层代码中。我认为底部 sheet 覆盖应用栏是不寻常的。
为了演示此通知,在此视频中,当拖动底部 sheet 时,应用栏会在底部 sheet 后面打开。
要更改此行为,如果触摸底部 sheet,我们会将 AppBarLayout.Behavior
的 onInterceptTouchEvent
覆盖为 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,然后我们必须撤消环境的影响。