BottomSheet Behavior In Android

This article talks about the creation of BottomSheet overlay in Android and its behavior.

Introduction 

BottomSheetBehavior is a type of behavior in which two or more layouts are placed on one another and the bottom layout performs the scrolling as other layouts expand the overlay and then collapse. This is why it is called BottomSheetBehavior.

The bottom view or layout can be dragged over the main layout or content so it becomes an overlay on the main content. These views include a layout, dialog, or a dialog fragment. In this article, we are mainly focusing on the layout and views only.

BottomSheetBehavior can be seen in Android maps applications in which the description of directions can be dragged from the bottom to the top. 
 
Let's start developing the BottomSheet behavior in our Android application.

Step 1

Include the design library to the gradle as shown below. Without this, you can't activate this behavior. See the build.gradle below.
  1. apply plugin: 'com.android.application'  
  2.   
  3. android {  
  4.     compileSdkVersion 28  
  5.     defaultConfig {  
  6.         applicationId "yourdomain.bottomsheetproj"  
  7.         minSdkVersion 21  
  8.         targetSdkVersion 28  
  9.         versionCode 1  
  10.         versionName "1.0"  
  11.         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"  
  12.     }  
  13.     buildTypes {  
  14.         release {  
  15.             minifyEnabled false  
  16.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
  17.         }  
  18.     }  
  19. }  
  20.   
  21. dependencies {  
  22.     implementation fileTree(dir: 'libs', include: ['*.jar'])  
  23.     implementation 'com.android.support:appcompat-v7:28.0.0'  
  24.     implementation 'com.android.support:design:28.0.0'  
  25.     implementation 'com.android.support.constraint:constraint-layout:1.1.3'  
  26.     testImplementation 'junit:junit:4.12'  
  27.     androidTestImplementation 'com.android.support.test:runner:1.0.2'  
  28.     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'  
  29. }  
Step 2

Now, we need two separate layouts. Put them under CoordinatorLayout. Let's see activity_main.xml.
  1. <android.support.design.widget.CoordinatorLayout   
  2.     xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     >  
  6.   
  7.     <include  
  8.         android:id="@+id/main_layout"  
  9.         layout="@layout/main_upper_layout" />  
  10.   
  11.     <include  
  12.         android:id="@+id/bottom_sheet_lay"  
  13.         layout="@layout/lower_layout" />  
  14.   
  15. </android.support.design.widget.CoordinatorLayout>  
We can see there is a CoordinatorLayout and two layouts that we are including in it. The first one is main_upper_layout and the other one is lower_layout.

Let's see the main_upper_layout.xml file.
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="300dp"  
  5.     android:background="#D3D3D3">  
  6.   
  7.   
  8.     <TextView  
  9.         android:id="@+id/heading_textview"  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content"  
  12.         android:layout_alignParentStart="true"  
  13.         android:layout_alignParentTop="true"  
  14.         android:layout_marginTop="21dp"  
  15.         android:gravity="center"  
  16.         android:padding="20dp"  
  17.         android:text="This is upper layout show views of your choice." />  
  18.   
  19.   
  20.     <Button  
  21.         android:id="@+id/expand_button"  
  22.         android:layout_width="wrap_content"  
  23.         android:layout_height="wrap_content"  
  24.         android:layout_alignParentTop="true"  
  25.         android:layout_centerHorizontal="true"  
  26.         android:layout_marginTop="106dp"  
  27.         android:text="Expand bottom sheet" />  
  28.   
  29.     <Button  
  30.         android:id="@+id/collapse_button"  
  31.         android:layout_width="wrap_content"  
  32.         android:layout_height="wrap_content"  
  33.         android:layout_below="@id/expand_button"  
  34.         android:layout_centerHorizontal="true"  
  35.         android:layout_marginTop="20dp"  
  36.         android:text="Collapse bottom sheet" />  
  37.   
  38. </RelativeLayout>  
Now, we need the lower layout. So, let us observe lower_layout.xml.
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="wrap_content"  
  5.     xmlns:app="http://schemas.android.com/apk/res-auto"  
  6.     android:padding="10dp"  
  7.     android:focusable="true"  
  8.     android:focusableInTouchMode="true"  
  9.     android:orientation="vertical"  
  10.     app:behavior_hideable="false"  
  11.     app:behavior_peekHeight="0dp"  
  12.     app:layout_behavior="android.support.design.widget.BottomSheetBehavior"  
  13.     android:background="#ffffff">  
  14.   
  15.     <TextView  
  16.         android:id="@+id/lower_sheet_textview"  
  17.         android:layout_width="wrap_content"  
  18.         android:layout_height="wrap_content"  
  19.         android:layout_marginRight="40dp"  
  20.         android:text="Hi! this is bottom sheet layout scroll above to expand and scroll down to collapse." />  
  21.   
  22.   
  23.     <ImageView  
  24.         android:id="@+id/image_view"  
  25.         android:layout_width="25dp"  
  26.         android:layout_height="25dp"  
  27.         android:src="@drawable/ic_arrow_up_icon"  
  28.         android:layout_alignParentEnd="true"  
  29.         android:layout_centerHorizontal="true"  
  30.         android:layout_marginRight="10dp"/>  
  31.   
  32.   
  33.     <TextView  
  34.         android:id="@+id/textview1"  
  35.         android:layout_width="wrap_content"  
  36.         android:layout_height="wrap_content"  
  37.         android:text="sample text 1"  
  38.         android:layout_marginTop="10dp"  
  39.         android:layout_below="@id/lower_sheet_textview"/>  
  40.     <TextView  
  41.         android:id="@+id/textview2"  
  42.         android:layout_width="wrap_content"  
  43.         android:layout_height="wrap_content"  
  44.         android:text="sample text 2"  
  45.         android:layout_marginTop="10dp"  
  46.         android:layout_below="@id/textview1"/>  
  47.     <TextView  
  48.         android:id="@+id/textview3"  
  49.         android:layout_width="wrap_content"  
  50.         android:layout_height="wrap_content"  
  51.         android:text="sample text 3"  
  52.         android:layout_marginTop="10dp"  
  53.         android:layout_below="@id/textview2"/>  
  54.     <TextView  
  55.         android:id="@+id/textview4"  
  56.         android:layout_width="wrap_content"  
  57.         android:layout_height="wrap_content"  
  58.         android:text="sample text 4"  
  59.         android:layout_marginTop="10dp"  
  60.         android:layout_below="@id/textview3"/>  
  61.   
  62. </RelativeLayout>  
Now, we are just taking the random height for both the layouts because we will set the height and peakHeight dynamically.

MainActivity.java
  1. import android.os.Bundle;  
  2. import android.support.annotation.NonNull;  
  3. import android.support.design.widget.BottomSheetBehavior;  
  4. import android.support.v7.app.AppCompatActivity;  
  5. import android.util.Log;  
  6. import android.view.View;  
  7. import android.widget.Button;  
  8. import android.widget.ImageView;  
  9. import android.widget.RelativeLayout;  
  10.   
  11.   
  12. public class MainActivity extends AppCompatActivity implements View.OnClickListener {  
  13.   
  14.     Button expandButton;  
  15.     Button collapseButton;  
  16.   
  17.     RelativeLayout mainUpperLayout;  
  18.     RelativeLayout bottomSheetLayout;  
  19.     BottomSheetBehavior bottomSheetBehavior;  
  20.     ImageView expandCollapsedImgView;  
  21.   
  22.   
  23.     @Override  
  24.     protected void onCreate(Bundle savedInstanceState) {  
  25.         super.onCreate(savedInstanceState);  
  26.         setContentView(R.layout.activity_main);  
  27.   
  28.         expandCollapsedImgView = findViewById(R.id.image_view);  
  29.         mainUpperLayout = findViewById(R.id.main_layout);  
  30.         bottomSheetLayout = findViewById(R.id.bottom_sheet_lay);  
  31.         expandButton = findViewById(R.id.expand_button);  
  32.         expandButton.setOnClickListener(this);  
  33.   
  34.         collapseButton = findViewById(R.id.collapse_button);  
  35.         collapseButton.setOnClickListener(this);  
  36.         setUp();  
  37.   
  38.   
  39.     }  
  40.   
  41.     @Override  
  42.     public void onClick(View view) {  
  43.   
  44.         switch (view.getId()) {  
  45.   
  46.             case R.id.expand_button:  
  47.   
  48.                 BottomSheetBehavior.from(bottomSheetLayout)  
  49.                         .setState(BottomSheetBehavior.STATE_EXPANDED);  
  50.                 break;  
  51.   
  52.             case R.id.collapse_button:  
  53.   
  54.                 BottomSheetBehavior.from(bottomSheetLayout)  
  55.                         .setState(BottomSheetBehavior.STATE_COLLAPSED);  
  56.                 break;  
  57.   
  58.                 default:  
  59.                     break;  
  60.   
  61.         }   
  62.     }    
  63.     private void setUp() {  
  64.   
  65.         int totalAvailableHeight = (ScreenUtils.getScreenHeight(this) - ((ScreenUtils.getStatusBarHeight(this)) + ScreenUtils.getActionBarHeight(this)));  
  66.   
  67.         Log.e("available screen height", String.valueOf(totalAvailableHeight));  
  68.   
  69.         int upperHeight = totalAvailableHeight - 200;  
  70.   
  71.         int bottomHeight = totalAvailableHeight - upperHeight;  
  72.   
  73.         mainUpperLayout.getLayoutParams().height = upperHeight;  
  74.         mainUpperLayout.requestLayout();  
  75.   
  76.         bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetLayout);  
  77.         bottomSheetBehavior.setPeekHeight(bottomHeight);  
  78.         bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {  
  79.             @Override  
  80.             public void onStateChanged(@NonNull View bottomSheet, int newState) {  
  81.                 switch (newState) {  
  82.                     case BottomSheetBehavior.STATE_EXPANDED: {  
  83.   
  84.                         expandCollapsedImgView.setImageResource(R.drawable.ic_expand_arrow);    
  85.                     }  
  86.                     break;  
  87.                     case BottomSheetBehavior.STATE_COLLAPSED: {  
  88.                         expandCollapsedImgView.setImageResource(R.drawable.ic_arrow_up_icon);  
  89.   
  90.                     }  
  91.                     break;  
  92.                 }  
  93.             }  
  94.   
  95.             @Override  
  96.             public void onSlide(@NonNull View bottomSheet, float slideOffset) {  
  97.   
  98.             }  
  99.         });  
  100.     }  
  101. }  
We are setting the peakHeight to 200px for bottomSheetLayout. Calculate the entire height of both the layouts dynamically with the help of ScreenUtils class. Moreover, we can calculate the height of statusbar, toolbar from the method getStatusBarHeight(Context context) and getActionBarHeight(Context context) respectively, as shown in the code below.

ScreenUtils.java
  1. import android.content.Context;  
  2. import android.util.DisplayMetrics;  
  3. import android.util.TypedValue;  
  4. import android.view.WindowManager;  
  5.   
  6.   
  7. public class ScreenUtils {  
  8.   
  9.     private ScreenUtils() {  
  10.         // This utility class is not publicly instantiable  
  11.     }  
  12.   
  13.     public static int getScreenWidth(Context context) {  
  14.         WindowManager windowManager = (WindowManager) context  
  15.                 .getSystemService(Context.WINDOW_SERVICE);  
  16.         DisplayMetrics dm = new DisplayMetrics();  
  17.         windowManager.getDefaultDisplay().getMetrics(dm);  
  18.         return dm.widthPixels;  
  19.     }  
  20.   
  21.     public static int getScreenHeight(Context context) {  
  22.         WindowManager windowManager = (WindowManager) context  
  23.                 .getSystemService(Context.WINDOW_SERVICE);  
  24.         DisplayMetrics dm = new DisplayMetrics();  
  25.         windowManager.getDefaultDisplay().getMetrics(dm);  
  26.         return dm.heightPixels;  
  27.     }  
  28.   
  29.     public static int getStatusBarHeight(Context context) {  
  30.         int result = 0;  
  31.         int resourceId = context.getResources()  
  32.                 .getIdentifier("status_bar_height""dimen""android");  
  33.         if (resourceId > 0) {  
  34.             result = context.getResources().getDimensionPixelSize(resourceId);  
  35.         }  
  36.         return result;  
  37.     }  
  38.   
  39.     public static int getActionBarHeight(Context context){  
  40.         int actionBarHeight=0;  
  41.         TypedValue tv = new TypedValue();  
  42.         if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {  
  43.             actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,context.getResources().getDisplayMetrics());  
  44.         }  
  45.         return actionBarHeight;  
  46.     }  
  47. }  
We can see in the MainActivity.java file that the bottom layout can be dragged to the top by simply scrolling the bottom layout. However, we have taken two buttons to expand and collpase the bottom sheet layout. See in the output below.

Output

First state is collapsed state


Secondly, tap on the EXPAND BOTTOM SHEET button or simply drag the bottom layout which shows the text and image so it will be expanded, as shown below.

Second is Expanded State.


Now, it is in the expanded state; you can put it in a normal collapsed state via the COLLAPSE BOTTOM SHEET button or simply by scrolling down. The arrow indicates the action here. Now, when it shows downward, then it is in an expanded state. If it is upward, then it is in the collapsed state.

Third one is again a Normal Collapsed State.


Conclusion


In this article, we learned about the BottomSheet behavior of views or layout which are placed under CoordinatorLayout. This behavior is very simple to achieve and enhances the user experience and look and feel of the Android application as well.