nor0x
nor0x

Reputation: 1213

Persistent BottomSheet below ActionBar

I have an app layout with a custom toolbar and a persistent BottomSheet - both inside of a CoordinatorLayout.

On a button click I want to show the BottomSheet. Right now the sheet is displayed fullscreen and overlays the toolbar. By setting the app theme to Theme.AppCompat.Light.DarkActionBar the BottomSheet stays below the ActionBar, but the bar cannot be customized.

Is there a way to limit the height of the persitent BottomSheet to fullscreen - ActionBar height?

This is my code in activity_main.xml

<android.support.design.widget.CoordinatorLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:attrs="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.test.MainActivity">

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

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        app:elevation="20dp"
        android:elevation="20dp"
        android:layout_height="?attr/actionBarSize">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            app:elevation="20dp"
            android:elevation="20dp"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"/>
    </android.support.design.widget.AppBarLayout>
    </LinearLayout>
    <include layout="@layout/bottom_sheet_additem"/>
</CoordinatorLayout>

Here is the code of sheet_bottom.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/bottomSheetLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorAccent"
    app:behavior_hideable="true"
    app:behavior_peekHeight="0dp"
    android:fitsSystemWindows="true"
    app:layout_behavior="@string/bottom_sheet_behavior">

    <TextView
        android:id="@+id/bottomsheet_text"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:text="Lorem Ipsum Dolor..."
        android:textColor="#FFFFFF" />
</RelativeLayout>

Screenshot The image on the left hand side shows the BottomSheet which stops below the Toolbar - which is not working with my current code. Currently it looks like the picture on the right.

Upvotes: 11

Views: 9191

Answers (5)

worksmarter
worksmarter

Reputation: 1

+1 for updating the margin programmatically. While the hidden part of the BottomSheet can be offset by increasing the peek height, using just the original solution above means the margin projects onto the underlying UI elements so the scroll action area projects off the actual BottomSheet onto the other UI.

Using the onStateChanged method in the BottomSheetCallback means acting on the event that the BottomSheet has already expanded or collapsed. Adding or removing the margin at this stage can result in seeing 'jerky' behaviour where for example the sheet momentarily reaches the fully expanded state covering the appbar before the margin is then programmatically applied to shift the UI components down resulting in a 'flash' as this applies.

Instead I used the onSlide method to detect when the BottomSheet was being slid up or down and added or removed the margins only once the sheet was half way through the transition. If the margins are applied too early in the slide motion then again the user can see the BottomSheet UI jumping up or down not long after initiating the action (they are less likely to notice this at the half way point if they have done a 'fling' up or down motion.

Also I found it worked best to fetch the height of the AppBar and the status bar and use those to set the required padding value for accurate placement in expanded mode.

This challenge can be avoided altogether by using a widget to trigger the BottomSheet state change programmatically.

@Override
        public void onSlide(View bottomSheet, float slideOffset) {
            boolean inRangeExpanding = oldOffSet < slideOffset;
            boolean inRangeCollapsing = oldOffSet > slideOffset;
            oldOffSet = slideOffset;
            if (inRangeCollapsing && slideOffset < 0.5f) {
                //reset padding on top of bottomsheet so there is no padding/overlap onto underlying sheet (which overlaps underlying sheet and so interfers with scrolling behaviour
                bSheetView.setPadding(0,10,0,0);
                Log.d(TAG,"onSlide STATE_COLLAPSING");
            } else if(inRangeExpanding && slideOffset > 0.5f){
                //reset padding on top of bottomsheet so there is padding/overlap onto underlying sheet so it does not write over the top of the menu appbar
                bSheetView.setPadding(0, topMargin,0,0);
                Log.d(TAG,"onSlide STATE_EXPANDING");
            }
        }

Upvotes: 0

Zain
Zain

Reputation: 40878

The accepted answer works when you expand the bottom sheet to the full screen, but when collapsing it, it adds additional margin that makes a part of the collapsing layout hidden below the screen, so I decided to put the margin programmatically by listening to the collapse/hidden status of the BottomSheet.

First add the BottomSheet within a CoordintorLayout in xml

And add below callback listener.

mBottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
    @Override
    public void onStateChanged(@NonNull View bottomSheet, int newState) {

        CoordinatorLayout bottomSheet = findViewById(..); // inflate the bottom sheet
        CoordinatorLayout.LayoutParams  layoutParams = 
             (CoordinatorLayout.LayoutParams) bottomSheet.getLayoutParams();

        if (newState == BottomSheetBehavior.STATE_COLLAPSED) 
            layoutParams.setMargins(0, 0, 0, 0); // remove top margin
         else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
            layoutParams.setMargins(0, 100, 0, 0); // add top margin
        
        bottomSheet.setLayoutParams(layoutParams);
    }

    @Override
    public void onSlide(@NonNull View bottomSheet, float slideOffset) {

    }
});

Upvotes: 2

Rafaela Louren&#231;o
Rafaela Louren&#231;o

Reputation: 1166

I had the same problem... I don't know if it's the best solution, but for now, worked for me.

Try to put your include inside another CoordinatorLayout in your activity_main.xml, with a marginTop like this:

<android.support.design.widget.CoordinatorLayout
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:attrs="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.test.MainActivity">

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

        <android.support.design.widget.AppBarLayout
          android:layout_width="match_parent"
          app:elevation="20dp"
          android:elevation="20dp"
          android:layout_height="?attr/actionBarSize">

          <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            app:elevation="20dp"
            android:elevation="20dp"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"/>
        </android.support.design.widget.AppBarLayout>
    </LinearLayout>

    <android.support.design.widget.CoordinatorLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_marginTop="56dp">
      
      <include layout="@layout/bottom_sheet_additem"/>

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

</CoordinatorLayout>

I hope it helps.

Upvotes: 27

Maycon Medeiros
Maycon Medeiros

Reputation: 21

Your sheet_bottom should look like this

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/activity_main">

 <RelativeLayout
    android:id="@+id/bottom_sheet"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:behavior_hideable="true"
    app:behavior_peekHeight="?android:attr/actionBarSize"
    app:elevation="@dimen/size_5dp"
    app:layout_behavior="@string/bottom_sheet_behavior">
 </RelativeLayout>

 </androidx.coordinatorlayout.widget.CoordinatorLayout>

Upvotes: 2

user2067340
user2067340

Reputation: 231

We can use app:layout_behavior instead of fixed height

<android.support.design.widget.CoordinatorLayout
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:layout_behavior="@string/appbar_scrolling_view_behavior">

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

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

Upvotes: 3

Related Questions