android – Scrolling is not working with CoordinatorLayout + parallax image + BottomSheetLayout-ThrowExceptions

Exception or error:

Introduction

I have an activity, which implements a common pattern with parallax header image and scrolling content using CoordinatorLayout, AppBarLayout and CollapsingToolbarLayout. My xml layout looks like this:

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

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

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

            <TextView
                android:background="@color/colorAccent"
                android:gravity="center"
                android:layout_height="250dp"
                android:layout_width="match_parent"
                android:text="ParallaxImage"
                app:layout_collapseMode="parallax"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_height="?attr/actionBarSize"
                android:layout_width="match_parent"
                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:id="@+id/content"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/text_margin"
            android:layout_width="wrap_content"
            android:text="@string/large_text"/>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

As you can see in the gif animation below, everything works correctly. You can scroll the whole screen from the content NestedScrollView as well as from the Toolbar or the parallax View.

AppBarLayout + NestedScrollView

Problem

Google introduced a BottomSheetBehavior class (Android design support library 23.2) to help developers to implement Bottom sheets. My xml layout with Bottom sheet looks like this:

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

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">
        <!-- ommited -->
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <!-- ommited -->
    </android.support.v4.widget.NestedScrollView>

    <LinearLayout
        android:id="@+id/bottomSheet"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:background="@android:color/holo_blue_bright"
        android:orientation="vertical"
        app:behavior_peekHeight="?attr/actionBarSize"
        app:layout_behavior="android.support.design.widget.BottomSheetBehavior">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:gravity="center_vertical"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:text="BottomSheetLayout"
            android:textColor="@android:color/white"/>

        <android.support.v4.widget.NestedScrollView
            android:id="@+id/bottomSheetContent"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="@android:color/holo_green_dark"
                android:padding="16dp"
                android:text="@string/large_text"
                android:textColor="@android:color/white"/>

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

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

And the result looks like this:

enter image description here

As you can see, Now I am not able to scroll, if I start to scroll from the parallax View. Scrolling from content NestedScrollView and from the Toolbar works as expected.

Question

How can I manage the scrolling to work from parallax View as well (the same way as in the first gif animation)? It seems that the BottomSheetBehavior intercepts touch events and prevents the AppBarLayout (AppBarLayoutBehavior) to handle the scroll. But the weird thing is that scrolling from Toolbar works and both parallax View and Toolbar are children of the AppBarLayout.

How to solve:

I’m copying paste the XML that I’m using. It’s working on Samsung Galaxy S3, Huawei Mate 8 and Moto by the way and I have not tried on emulator (I removed all business stuff and just put dummy widgets).

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

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

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

        <ImageView
            android:id="@+id/backdrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:fitsSystemWindows="true"
            app:layout_collapseMode="parallax"/>


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

    </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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="16dp">

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="16dp">

            <LinearLayout
                style="@style/Widget.CardContent"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo." />

            </LinearLayout>

        </android.support.v7.widget.CardView>

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp">

            <LinearLayout
                style="@style/Widget.CardContent"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="TITLE"
                android:textAppearance="@style/TextAppearance.AppCompat.Title" />

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

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

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

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

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

                </LinearLayout>

            </android.support.v7.widget.CardView>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>


<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:behavior_peekHeight="100dp"
    android:fitsSystemWindows="true"
    app:layout_behavior="android.support.design.widget.BottomSheetBehavior">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="16dp"
        android:background="@android:color/white"
        android:padding="15dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="BOOTOMSHEET TITLE"
            android:textAppearance="@style/TextAppearance.AppCompat.Title" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Button1"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="text 2"
            android:layout_margin="10dp"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="text 3"
            android:layout_margin="10dp"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="text 4"
            android:layout_margin="10dp"/>


        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="320dp"
            android:background="@color/colorAccent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="Your remaining content here"
                android:textColor="@android:color/white" />

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

###

I think you should use NestedScrollView with the BottomSheetBehavior,replace blow as the bootemSheet!

<android.support.v4.widget.NestedScrollView
    android:id="@+id/bottomSheet"
    android:layout_width="match_parent"
    android:layout_height="400dp"
    android:background="@android:color/holo_blue_bright"
    android:orientation="vertical"
    app:behavior_peekHeight="?attr/actionBarSize"
    app:layout_behavior="android.support.design.widget.BottomSheetBehavior">

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

        <TextView
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:gravity="center_vertical"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:text="BottomSheetLayout"
            android:textColor="@android:color/white"/>

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

            <TextView
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="@android:color/holo_green_dark"
                android:padding="16dp"
                android:text="@string/large_text"
                android:textColor="@android:color/white"/>
        </ScrollView>
    </LinearLayout>

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

NestedScrollView can know how to nested with the toolbar,not the LinearLayout!

wish to help!!

###

It seems like a bug in BottomSheetBehavior code probably, cause if you try to debug the code of CoordinatorLayout you will see that when you touch your parallax View it will be determined as a layout with BottomSheetBehavior instead of HeaderBehavior.

So the quick fix that I’ve found is to set OnTouchListener that always returns true to your parallax view:

View parallaxView = findViewById(R.id.parallax_view);
parallaxView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return true;
    }
});

Of course, don’t forget to set android:id="@+id/parallax_view" to your view with parallax collapse mode.

Hope it helps!

###

try this

 <LinearLayout
        android:id="@+id/bottomSheet"
        android:layout_width="match_parent"
        android:layout_height="400dp">

change height 400dp to wrap_content

hope works for you

###

The issue can be solved by moving the NestedScrollView (or fragment containing a NestedScrollView in my case) outside the CoordinatorLayout in a FrameLayout and placing a dummy view where the NestedScrollView was, like this:

<FrameLayout>
    <CoordinatorLayout>
        <AppBarLayout>
            ...
        </AppBarLayout>
        <View
            android:id="@+id/bottomSheet"
            app:layout_behavior="android.support.design.widget.BottomSheetBehavior" />
    </CoordinatorLayout>
    <NestedScrollView
        android:id="@+id/bottomSheetContent">
        ...
    </NestedScrollView>
</FrameLayout>

Finally, add a BottomSheetCallback to the dummy view that translates the actual content view while sliding:

@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
    bottomSheetContent.setTranslationY((1f - slideOffset) * bottomSheetContent.getHeight());
}

Apparently, this is the only way to prevent a CoordinatorLayout from reacting to (and intercepting) any touch events. #JustGoogleThings

###

I came up with my own answer:

  • add app:layout_behavior="...AppBarLayoutCustomBehavior" to your appBarLayout.

  • create that class and extend from AppBarLayout.Behavior

  • override onStartNestedScroll and onNestedFling

  • create a flag like scrollDenial, and add it to both methods below, then call super (scrollDenial && super.onStartNestedScroll…)

  • now, you need to update your denial condition on every onStartNestedScroll. I did something like this:

    https://gist.github.com/recoverrelax/8dd37b54910d70b5cd6a130a070c51e9

Ps. I’m sorry, but this is Kotlin :p It shouldn’t be hard to understand tho. It solved the bottomSheet problem.

Leave a Reply

Your email address will not be published. Required fields are marked *