ZOFTINO.COM

Android Bottom Sheet

Bottom sheet is displayed at bottom of the screen. A view can be displayed as bottom sheet by attaching bottom sheet behavior to it. Bottom sheet behavior extends Behavior which has methods that coordinator layout calls to provide behavior to child views in response to motion events and positional changes of child views.

Bottom sheet behavior has been provided as part of design support library that supports or helps build components that implement material design guide lines. In order to use bottom sheet behavior, you must include design support library dependency in your project.

Here main component that support material design is coordinator layout. In order to implement bottom sheet behavior in your app, the view you want to show as bottom sheet needs to be a direct child of coordinator layout.

Bottom Sheet Implementation

I am going to show how to implement bottom sheet with coupons app example. On a screen that shows list of coupons for a store, a bottom sheet will be displayed on clicking about store button, showing description about the store. For this, first we need to create activity layout xml with coordinator layout as root. In the coordinator layout, nested scroll view is used to shows list of coupons and second nested scroll view is for bottom sheet.

In this example, as nested scroll view is shown as bottom sheet, it is attached bottom sheet behavior using layout_behavior attribute. Within nested scroll view, text views are used to show content. You can get the bottom sheet behavior in the code and set other attributes. We are interested in setting state of the bottom sheet. You can set bottom sheet sate by using setState method to one of the states such as expanded, collapsed, hidden etc.

Android Bottom Sheet example

android bottom sheet example

Android Bottom Sheet Code

activity layout

 <?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/bottom_sheet_activity"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.zoftino.materialdesign.BottomSheetActivity">
    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorBrn"
        android:id="@+id/appbar">
        <android.support.v7.widget.Toolbar android:id="@+id/z_toolbar" android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary"
            android:elevation="4dp" app:layout_scrollFlags="scroll|enterAlways">
        </android.support.v7.widget.Toolbar>

        <android.support.design.widget.TabLayout
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:id="@+id/tabL">
            <android.support.design.widget.TabItem
                android:text="@string/stores"
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:id="@+id/stores"></android.support.design.widget.TabItem>

            <android.support.design.widget.TabItem
                android:text="@string/coupons"
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:id="@+id/coupons"></android.support.design.widget.TabItem>

            <android.support.design.widget.TabItem
                android:text="@string/cashback"
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:id="@+id/cashback"></android.support.design.widget.TabItem>
        </android.support.design.widget.TabLayout>
    </android.support.design.widget.AppBarLayout>
    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="@dimen/activity_vertical_margin"
            android:layout_marginBottom="@dimen/activity_vertical_margin"
            android:orientation="horizontal">
        <TextView
            android:id="@+id/str_nme"
            android:textSize="20dp"
            android:layout_weight="1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></TextView>
        <Button
            android:text="About Store"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/abt_stre"
            android:onClick="showAboutStore"></Button></LinearLayout>
        <TextView
            android:id="@+id/coupons_lst"
            android:textSize="15dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></TextView>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="@color/colorGrn"
        app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin">
        <TextView
            android:id="@+id/str_nme_b"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textColor="@android:color/white"
            android:textSize="25dp"></TextView>
        <TextView
            android:id="@+id/about_stre"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textColor="@android:color/white"
            android:textSize="20dp"></TextView>
    </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

activity

package com.zoftino.materialdesign;

import android.os.Bundle;
import android.support.design.widget.BottomSheetBehavior;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.TextView;

public class BottomSheetActivity extends AppCompatActivity {
    private BottomSheetBehavior bottomSheet = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bottom_sheet_layout);

        Toolbar myToolbar = (Toolbar) findViewById(R.id.z_toolbar);
        setSupportActionBar(myToolbar);

        TextView tvStore =(TextView)findViewById(R.id.str_nme);
        tvStore.setText(CouponStoreData.storeNme);

        TextView tv =(TextView)findViewById(R.id.coupons_lst);
        tv.setText(CouponStoreData.arrayOfCoupons.toString());

        //bottom sheet part

        TextView bottomSheetHead =(TextView)findViewById(R.id.str_nme_b);
        bottomSheetHead.setText(CouponStoreData.storeNme);

        TextView aboutStore =(TextView)findViewById(R.id.about_stre);
        aboutStore.setText(CouponStoreData.aboutStore);

        bottomSheet = BottomSheetBehavior.from( findViewById(R.id.bottom_sheet));

        bottomSheet.setHideable(true);
        bottomSheet.setState(BottomSheetBehavior.STATE_HIDDEN);

        bottomSheet.setBottomSheetCallback(bottomSheetCallBack);
    }

    public void showAboutStore(View V){
        bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED);
    }

    private BottomSheetBehavior.BottomSheetCallback bottomSheetCallBack = new BottomSheetBehavior.BottomSheetCallback(){
        public void onSlide (View bottomSheet, float slideOffset){
            TextView aboutStore =(TextView)bottomSheet.findViewById(R.id.about_stre);
            if(slideOffset < 0.4){
                aboutStore.setText(CouponStoreData.miniAboutStore);
            }else{
                aboutStore.setText(CouponStoreData.aboutStore);
            }
        }
        public void onStateChanged (View bottomSheet, int newState){
            if(newState == BottomSheetBehavior.STATE_HIDDEN){
                TextView tv =(TextView)(BottomSheetActivity.this).findViewById(R.id.coupons_lst);
                tv.setText(CouponStoreData.arrayOfCashback.toString());
            }
        }
    };
}

Bottom sheet peek

In a collapsed state, bottom sheet is not visible to users. If you want to let users know that there exists a bottom sheet on the screen, then some part of bottom sheet can be displayed by setting the peek height of bottom sheet using setPeekHeight method of bottom sheet behavior object.

Bottom sheet event handling

You can handle bottom sheet events by implementing BottomSheetCallback callback methods. BottomSheetCallback has two call back methods. Method onStateChanged is called when bottom sheet’s state changes, for example, from collapsed state to expanded state. Another callback method onSlide is called when bottom sheet is dragged up or down.

In our example, on state change, we get latest coupons for the store and display. On scroll, we display mini version of about store. Please view above activity code to see complete implementation of BottomSheetCallback.

android bottom sheet behavior call back methods implementation

Bottom sheet behavior customization

Bottom sheet behavior can be customized by overriding certain coordinator call back methods such as onInterceptTouchEvent, onLayoutChild, onTouchEvent etc. I created a custom bottom sheet behavior by overriding onInterceptTouchEvent method, which gets called when any child of coordinator layout is touched. This custom bottom sheet behavior scrolls up bottom sheet to top when user touches other part of the screen.

To get this behavior in our app, we need to set our bottom sheet view’s layout_behavior attribute to custom bottom sheet behavior.

Custom bottom sheet behavior

 package com.zoftino.materialdesign;


import android.content.Context;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.widget.NestedScrollView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class ZoftinoBottomSheetBehavior extends BottomSheetBehavior {
    public ZoftinoBottomSheetBehavior(){
        super();
    }
    public ZoftinoBottomSheetBehavior(Context context, AttributeSet attrs){
        super(context, attrs);
    }
    @Override
    public boolean onInterceptTouchEvent (CoordinatorLayout parent, View child, MotionEvent event){

        super.onInterceptTouchEvent(parent, child, event);

        if(child instanceof NestedScrollView && event.getActionMasked() == MotionEvent.ACTION_DOWN){
            NestedScrollView nsv = (NestedScrollView)child;
            if(nsv.isShown()) {
                nsv.fullScroll(NestedScrollView.FOCUS_UP);
            }
        }
        return false;
    }
}
 

Modal bottom sheet or android bottom sheet dialog

Design support library provides BottomSheetDialogFragment and BottomSheetDialog classes to enable the creation of bottom sheet dialog. To create bottom sheet dialog, create a class which extends BottomSheetDialogFragment and override setupDialog method. In setupDialog method, inflate layout for bottom sheet dialog using view and set the dialog content.

Android bottom sheet dialog output

android bottom sheet dialog example

Android bottom sheet dialog code

Activity

package com.zoftino.materialdesign;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.TextView;

public class BottomSheetDialogActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bottom_sheet_dialog);

        Toolbar myToolbar = (Toolbar) findViewById(R.id.z_toolbar);
        setSupportActionBar(myToolbar);

        TextView tvStore =(TextView)findViewById(R.id.str_nme);
        tvStore.setText(CouponStoreData.storeNme);

        TextView tv =(TextView)findViewById(R.id.coupons_lst);
        tv.setText(CouponStoreData.arrayOfCoupons.toString());
    }
    public void showAboutStore(View view){
        ZoftinoBottomSheetDialogFragment bottomSheetDialogFragment =
                ZoftinoBottomSheetDialogFragment.newInstance((String)view.getTag());
        bottomSheetDialogFragment.show(getSupportFragmentManager(), (String)bottomSheetDialogFragment.getTag());

    }

}

Bottom sheet dialog fragment

package com.zoftino.materialdesign;

import android.app.Dialog;
import android.os.Bundle;
import android.support.design.widget.BottomSheetDialogFragment;
import android.view.View;
import android.widget.TextView;

public class ZoftinoBottomSheetDialogFragment extends BottomSheetDialogFragment {

    static ZoftinoBottomSheetDialogFragment newInstance(String storeNme) {
        ZoftinoBottomSheetDialogFragment zoftinoBottomSheetDialogFragment = new ZoftinoBottomSheetDialogFragment();
        Bundle args = new Bundle();
        args.putString("storeNme", storeNme);
        zoftinoBottomSheetDialogFragment.setArguments(args);
        return zoftinoBottomSheetDialogFragment;
    }

    @Override
    public void setupDialog(Dialog dialog, int style) {
        super.setupDialog(dialog, style);

        String storeNme = getArguments().getString("storeNme");
        //storeNme can be used to get data for the passed store

        View v = View.inflate(getContext(), R.layout.bottom_sheet_dialog_fragment,  null);

        TextView bottomSheetHead =(TextView)v.findViewById(R.id.str_nme_b);
        bottomSheetHead.setText(CouponStoreData.storeNme);

        TextView aboutStore =(TextView)v.findViewById(R.id.about_stre);
        aboutStore.setText(CouponStoreData.aboutStore);

        dialog.setContentView(v);
    }

}

Activity layout

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/bottom_sheet_activity"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.zoftino.materialdesign.BottomSheetDialogActivity">
    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorBrn"
        android:id="@+id/appbar">
        <android.support.v7.widget.Toolbar android:id="@+id/z_toolbar" android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary"
            android:elevation="4dp" app:layout_scrollFlags="scroll|enterAlways">
        </android.support.v7.widget.Toolbar>
    </android.support.design.widget.AppBarLayout>
    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="horizontal">
                <TextView
                    android:id="@+id/str_nme"
                    android:textSize="20dp"
                    android:layout_weight="1"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"></TextView>
                <Button
                    android:text="About Store"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:id="@+id/abt_stre"
                    android:onClick="showAboutStore"></Button></LinearLayout>
            <TextView
                android:id="@+id/coupons_lst"
                android:textSize="15dp"
                android:layout_width="match_parent"
                android:layout_height="match_parent"></TextView>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

Bottom sheet dialog layout

<LinearLayout
    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="wrap_content"
    android:background="@color/colorGrn"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin">
    <TextView
        android:id="@+id/str_nme_b"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textColor="@android:color/white"
        android:textSize="25dp"></TextView>
    <TextView
        android:id="@+id/about_stre"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:textColor="@android:color/white"
        android:textSize="20dp"></TextView>
</LinearLayout>