10

I have a CoordinatorLayout containing two NestedScrollViews, One has ScrollingViewBehavior and the other has BottomSheetBehavior and plays BottomSheet role, The problem is when I drag the BottomSheet, The first NestedScrollView scrolls too :

enter image description here

What I expect:
When I scroll BottomSheet, Another NestedScrollView should not scroll

What I tried:
I created a custom behavior and override onInterceptTouchEvent, On event that belongs to BottomSheet returned true and invoked dispatchTouchEvent on BottomSheet, It prevents the other NestedScrollView scrolls but BottomSheet can't scroll itself and click events on BottomSheet children did not work anymore.

Here's my layout :

<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>

and my 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>
Farshad Tahmasbi
  • 2,755
  • 2
  • 25
  • 42

1 Answers1

12

Updated with simpler solution.

The solution to your question can be divided into two sections:

  1. How to raise the bottom sheet without scrolling the appbar?
  2. How to lower the bottom sheet once raised.

To raise the bottom sheet without effected the appbar, we need to detect when the bottom sheet is responsible for the scroll and let the appbar refuse the nested scroll so it won't receive subsequent nested scroll events. This is accomplished in the onStartNestedScroll method of AppBarLayout.Behavior. (See code for MyAppBarBehavior below.)

We can now drag up the bottom sheet just like an independent sliding panel.

enter image description here

Now that we have dragged the bottom sheet up, can we drag it back down? Yes, and no. If the bottom sheet is scrolled to the top of its content, then we can drag the bottom sheet down if we touch the bottom sheet below the appbar. If we touch the bottom sheet over the appbar (which, of course, is behind the bottom sheet), then we cannot drag the bottom sheet down. This issue is present in the underlying code without our modifications. I think that it is just unusual for a bottom sheet to overlay an appbar.

To demonstrate this notice, in this video, that the appbar opens up behind the bottom sheet when the bottom sheet is dragged.

enter image description here

To change this behavior, we will override the onInterceptTouchEvent of the AppBarLayout.Behavior to return false if the bottom sheet is being touched.

Here is the new 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);
    }
}

(I shouldn't have identified the bottom sheet with a specific id, but this is just a demonstration.)

Add the custom behavior to the appbar:

<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">

Here is the final result:

enter image description here

There may be other ways to accomplish this. I would probably use an existing sliding panel library such as AndroidSlidingUpPanel instead of placing a bottom sheet in a nested scrolling environment where we must then undo the effects of the environment.

Cheticamp
  • 50,205
  • 8
  • 64
  • 109
  • Thanks for response, What I mentioned above as code was just a sample, Actually I need to use `RecyclerView` and Some other `View` s in my `BottomSheet`, In that case `ScrollView` is not working for me becuase it should contain `RecyclerView` – Farshad Tahmasbi Mar 07 '18 at 19:44
  • @ҒάгՏҺαԃッ So, how would that work? You have a nested scroll view for a bottom sheet and within that you have a `RecyclerView` and some other non-scrolling views. When is the `RecyclerView` permitted to scroll? Just when the bottom sheet is expanded or other times, too? – Cheticamp Mar 07 '18 at 20:14
  • 1
    Only if it is expanded RecyclerView is permitted to scroll – Farshad Tahmasbi Mar 08 '18 at 00:48
  • @ҒάгՏҺαԃッ If there is no interaction between the bottom sheet and the other `CoordinatorLayout` views, isn't this just a sliding panel like [AndroidSlidingUpPanel](https://github.com/umano/AndroidSlidingUpPanel)? Are you using any specific bottom sheet features such as `BottomSheetBehavior.BottomSheetCallback`? – Cheticamp Mar 08 '18 at 17:35
  • I already considered the library you mentioned as the plan B (after I posted my question here) and It worked but I am curious If there is any way to consume scrolls by BottomSheet and prevent passing it to the other views, Also what you suggested here is a possible solution if I define RecyclerView's height WRAP_CONTENT, In that case no nested scrolls needed, right? – Farshad Tahmasbi Mar 08 '18 at 18:56
  • @ҒάгՏҺαԃッ The nested scrolling framework is very flexible, so I think that it is possible. As for using `wrap_content` on the `RecyclerView`, I am not sure that would work, but you can give it a try. I have noticed that the bottom sheet interaction with the `AppBarLayout` varies depending upon how high the bottom sheet is. If you make you bottom sheet fall just below the fully expanded appbar when the bottom sheet itself is fully expanded, you will find that it works the way you expect - not a solution, but an interesting observation IMO. – Cheticamp Mar 09 '18 at 03:30
  • I didn't get it, You mean put BottomSheet in the AppbarLayout or what? – Farshad Tahmasbi Mar 09 '18 at 11:43
  • @ҒάгՏҺαԃッ Updated answer. – Cheticamp Mar 10 '18 at 00:10
  • Thanks a lot for your effort, I thought it was all about `ScrollingViewBehavior`, But as you said It turns out that the issue is with `AppBarLayout` behavior, I don't know if you tested your solution or not, But when I tried it the second part of my question (according to your division) did not solved, I'm not sure missing something or not So I edited your solution with few lines of code and it worked well, Please check it out, Regards – Farshad Tahmasbi Mar 10 '18 at 08:12
  • @ҒάгՏҺαԃッ Glad it's working for you. I don't see the code you had to change to get the second part working. – Cheticamp Mar 10 '18 at 13:22
  • My bad, You can check it now – Farshad Tahmasbi Mar 10 '18 at 14:00
  • @ҒάгՏҺαԃッ Good catch. That is a defect with the underlying code and not with the custom code. It exhibits the same behavior without the custom `AppBarLayout.Behavior` where swiping down on a collapsed bottom sheet with continue to open up the appBar. – Cheticamp Mar 10 '18 at 14:37
  • Yes, Perhaps I should dig into nested scrolling framework ASAP! Anyway thanks for help – Farshad Tahmasbi Mar 10 '18 at 14:41
  • @ҒάгՏҺαԃッ Modified code. We don't need to check the state of the bottom sheet in the custom appBar behavior. – Cheticamp Mar 10 '18 at 15:34
  • I tried it before, didn't work, That's why I check the state :) – Farshad Tahmasbi Mar 11 '18 at 06:22
  • @ҒάгՏҺαԃッ The answer seemed overly complex to me. [Here](https://gist.github.com/Cheticamp/c14f3a8225fe21d2b24fb7993b070386) is an alternate approach that does not require any support code in the activity. All that is required is the new appBar behavior. – Cheticamp Mar 11 '18 at 15:45
  • Yes, I checked it, It's a better approach to me, An update would be nice If you don't mind. – Farshad Tahmasbi Mar 12 '18 at 06:31