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.
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.
<?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>
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());
}
}
};
}
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.
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.
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.
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;
}
}
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.
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());
}
}
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);
}
}
<?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>
<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>