58

The design support library v. 23.2 introduced BottomSheetBehavior, which allows childs of a coordinator to act as bottom sheets (views draggable from the bottom of the screen).

What I’d like to do is to have, as a bottom sheet view, the following view (the typical coordinator + collapsing stuff):

<CoordinatorLayout
    app:layout_behavior=“@string/bottom_sheet_behavior”>

   <AppBarLayout>
        <CollapsingToolbarLayout>
           <ImageView />
        </CollapsingToolbarLayout>
    </AppBarLayout>

    <NestedScrollView>
        <LinearLayout>
            < Content ... />
        </LinearLayout>
    </NestedScrollView>

</CoordinatorLayout>

Unfortunately, bottom sheet views should implement nested scrolling, or they won’t get scroll events. If you try with a main activity and then load this view as a bottom sheet, you’ll see that scroll events only act on the “sheet” of paper, with some strange behavior, as you can see if you keep reading.

I am pretty sure that this can be handled by subclassing CoordinatorLayout, or even better by subclassing BottomSheetBehavior. Do you have any hint?

Some thoughts

  • requestDisallowInterceptTouchEvent() should be used, to steal events from the parent in some conditions:

    • when the AppBarLayout offset is > 0
    • when the AppBarLayout offset is == 0, but we are scrolling up (think about it for a second and you’ll see)
  • the first condition can be obtained by setting an OnOffsetChanged to the inner app bar;

  • the second requires some event handling, for example:

    switch (MotionEventCompat.getActionMasked(event)) {
        case MotionEvent.ACTION_DOWN:
            startY = event.getY();
            lastY = startY;
            userIsScrollingUp = false;
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            userIsScrollingUp = false;
            break;
        case MotionEvent.ACTION_MOVE:
            lastY = event.getY();
            float yDeltaTotal = startY - lastY;
            if (yDeltaTotal > touchSlop) { // Moving the finger up.
                userIsScrollingUp = true;
            }
            break;
    }
    

Issues

Needless to say, I can’t make this work right now. I am not able to catch the events when the conditions are met, and not catch them in other cases. In the image below you can see what happens with a standard CoordinatorLayout:

  • The sheet is dismissed if you scroll down on the appbar, but not if you scroll down on the nested content. It seems that nested scroll events are not propagated to the Coordinator behavior;

  • There is also a problem with the inner appbar: the nested scroll content does not follow the appbar when it is being collapsed..

enter image description here

I have setup a sample project on github that shows these issues.

Just to be clear, desired behavior is:

  • Correct behavior of appbars/scroll views inside the sheet;

  • When sheet is expanded, it can collapse on scroll down, but only if the inner appbar is fully expanded too. Right now it does collapse with no regards to the appbar state, and only if you drag the appbar;

  • When sheet is collapsed, scroll up gestures will expand it (with no effect on the inner appbar).

An example from the contacts app (which probably does not use BottomSheetBehavior, but this is what I want):

enter image description here

natario
  • 24,954
  • 17
  • 88
  • 158
  • Did your NestedScrollView has this attribute? `app:layout_behavior="@string/appbar_scrolling_view_behavior"` – sakiM Mar 28 '16 at 10:56
  • @saki_M yes, see the [sample project](https://github.com/miav/BottomSheetCoordinatorLayout). – natario Mar 28 '16 at 12:25
  • I don't think nesting `CoordinatorLayout`'s is currently possible. Maybe take a look at Plaid's [ElasticDragDismissFrameLayout](https://github.com/nickbutcher/plaid/blob/ffa87f523e14043110584e69aa08e0e03ebed06b/app/src/main/java/io/plaidapp/ui/widget/ElasticDragDismissFrameLayout.java) implementation. – Markus Rubey Mar 28 '16 at 16:52
  • @Markus I am open to putting the inner Coordinator inside something else, I'm only interested in the final result. And I also would like to use BottomSheetBehavior. – natario Mar 28 '16 at 18:27
  • I know there are libraries out there who already achieve this (I have been using flipboard's implementation) but this time I'm interested in switching to BottomSheetBehavior. I will take a look at what you suggested and see if something comes to mind. – natario Mar 28 '16 at 18:29
  • I think you can refer [this project](https://github.com/ksoichiro/Android-ObservableScrollView). I had try it but for some reason didn't use it in [my project](https://play.google.com/store/apps/details?id=com.hihex.hexlink). – sakiM Mar 29 '16 at 02:56
  • @Markus took a look at that but I think it works only on API21+, since it relies upon nested scroll events. – natario Mar 29 '16 at 16:51
  • I am trying to achieve something similar and stuck on this same problem. Were you able to find any solution to this ? – nipun.birla Jul 27 '16 at 08:29
  • 1
    @nipun.birla yes, I did it myself. When I have time I will post my solution – natario Jul 27 '16 at 11:12
  • That will be helpful. Thank you in advance! – nipun.birla Jul 27 '16 at 11:29
  • Hey..Can you post the solution? – nipun.birla Jul 29 '16 at 20:02
  • Can you post your final code? – Anton Shkurenko Aug 15 '16 at 13:05
  • Sorry, travelling right now, I'll be back to work in the middle of september. – natario Aug 15 '16 at 16:47
  • Can you post your code now? – ahgpoug Oct 10 '16 at 18:16
  • was a solution found? – kassim Oct 20 '16 at 13:12
  • Anyone has a solution for this??? – Polar Nov 01 '16 at 05:17
  • 1
    I believe I got pretty far on this issue in [this sample project](https://github.com/laenger/BottomSheetCoordinatorLayout/tree/first_attempt), using a nested-scrolling-child-enabled `CoordinatorLayout` and a slightly adjusted `BottomSheetBehavior`. Have a look at [this commit](https://github.com/laenger/BottomSheetCoordinatorLayout/commit/af0ed662f5b52c12ac587fd62d87fb40bd77e4be) to see my key changes. You'll find gifs that illustrate what already works and what doesn't. – laenger Nov 24 '16 at 23:43
  • I don't know who upvoted my answer .But please go through my answer once .may it help you guys :-) #soreadytohelp – Ravindra Shekhawat Mar 24 '17 at 17:12

6 Answers6

6

I have finally released my implementation. Find it on Github or directly from jcenter:

compile 'com.otaliastudios:bottomsheetcoordinatorlayout:1.0.0’

All you have to do is using BottomSheetCoordinatorLayout as the root view for your bottom sheet. It will automatically inflate a working behavior for itself, so don’t worry about it.

I have been using this for a long time and it shouldn’t have scroll issues, supports dragging on the ABL etc.

natario
  • 24,954
  • 17
  • 88
  • 158
1

I have just followed the way you asked the above question and come up with solution that might need more explanation.Please follow your sample code and integrate the extra part in your xml to make it behave like BottomSheeet behaviour

<CoordinatorLayout>
   <AppBarLayout>
        <Toolbar
            app:layout_collapseMode="pin">
        </Toolbar>
    </AppBarLayout>
    <NestedScrollView
         app:layout_behavior=“@string/bottom_sheet_behavior” >
        <include layout="@layout/items" />
    </NestedScrollView>

    <!-- Bottom Sheet -->

     <BottomSheetCoordinatorLayout>
        <AppBarLayout
            <CollapsingToolbarLayout">
             <ImageView />
                <Toolbar />
            </CollapsingToolbarLayout>
        </AppBarLayout>
        <NestedScrollView">
            <include layout="@layout/items" />
        </NestedScrollView>
    </BottomSheetCoordinatorLayout>
</CoordinatorLayout>

Note : Solution that worked for me already explained in last comment of your question

Better explantion : https://github.com/laenger/BottomSheetCoordinatorLayout

Ravindra Shekhawat
  • 4,275
  • 1
  • 19
  • 26
1

i've followed laenger's initial github test project regarding this issue, and i'm glad to share you a solution for some of his issues since i needed this behavior in my app too.

this is a solution to his issue : ❌ toolbar sometimes collapses too early

to prevent this, you need to create your custom AppBarLayout.Behavior, since it is when you scroll up while still dragging that the AppBarLayout.behavior gets the scroll motion. We need to detect if its in STATE_DRAGGING and just return to avoid hiding/collapsing the toolbar up prematurely.

public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {

    private CoordinatorLayoutBottomSheetBehavior behavior;

    public CustomAppBarLayoutBehavior() {
    }

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

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
        behavior = CoordinatorLayoutBottomSheetBehavior.from(parent);
        return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
        if(behavior.getState() == CoordinatorLayoutBottomSheetBehavior.STATE_DRAGGING){
            return;
        }else {
            super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        }
    }

    @Override
    public void setDragCallback(@Nullable DragCallback callback) {
        super.setDragCallback(callback);
    }
}

this may be a good start on how we resolve the other issues:

❌ toolbar cannot be collapsed through drags

❌ main coordinator layout consumes some scroll

i'm not actually a good UI/animation person, but hardwork pays off understanding the code sometimes, finding the right callback/override function to implement.

set this as behavior to appbarlayout

<android.support.design.widget.AppBarLayout
    android:id="@+id/bottom_sheet_appbar"
    style="@style/BottomSheetAppBarStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="your.package.CustomAppBarLayoutBehavior">
user3115201
  • 111
  • 2
  • 14
0

If the first child is nestedscroll this will occurs some other problems. This solution is fixed my problem i hope also fix yours.

<CoordinatorLayout
    app:layout_behavior=“@string/bottom_sheet_behavior”>

   <AppBarLayout>
        <CollapsingToolbarLayout>
           <ImageView />
        </CollapsingToolbarLayout>
    </AppBarLayout>
</LinearLayout>
    <NestedScrollView>
        <LinearLayout>
            < Content ... />
        </LinearLayout>
    </NestedScrollView>
</LinearLayout>
</CoordinatorLayout>
Eren Utku
  • 1,731
  • 1
  • 18
  • 27
0

Try not to use NestedScrollView with LinearLayout, it has been causing problems in my app too. Just use only LinearLayout instead, works fine for me.

Try the following:

<CoordinatorLayout
app:layout_behavior=“@string/bottom_sheet_behavior”>

<AppBarLayout>
    <CollapsingToolbarLayout>
       <ImageView />
    </CollapsingToolbarLayout>
</AppBarLayout>

<LinearLayout>
     <!--don't forget to addd this line-->
     app:layout_behavior="@string/appbar_scrolling_view_behavior">

        < Content ... />
</LinearLayout>

zx485
  • 28,498
  • 28
  • 50
  • 59
Rahul
  • 26
  • 1
  • 6
-4

The layout for the full screen of appbar layout is as follows:-

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/detail_backdrop_height"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    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/ThemeOverlay.AppCompat.Light"
            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:paddingTop="24dp">

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/card_margin">

            <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="Info"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/cheese_ipsum" />

            </LinearLayout>

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

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/card_margin"
            android:layout_marginLeft="@dimen/card_margin"
            android:layout_marginRight="@dimen/card_margin">

            <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="Friends"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/cheese_ipsum" />

            </LinearLayout>

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

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/card_margin"
            android:layout_marginLeft="@dimen/card_margin"
            android:layout_marginRight="@dimen/card_margin">

            <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="Related"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/cheese_ipsum" />

            </LinearLayout>

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

    </LinearLayout>

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

<android.support.design.widget.FloatingActionButton
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    app:layout_anchor="@id/appbar"
    app:layout_anchorGravity="bottom|right|end"
    android:src="@drawable/ic_discuss"
    android:layout_margin="@dimen/fab_margin"
    android:clickable="true"/>

and after that you should implements AppBarLayout.OnOffsetChangedListener in your class and set offset of screen.