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 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.
- apply plugin: 'com.android.application'
-
- android {
- compileSdkVersion 28
- defaultConfig {
- applicationId "yourdomain.bottomsheetproj"
- minSdkVersion 21
- targetSdkVersion 28
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
- }
-
- dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'com.android.support:appcompat-v7:28.0.0'
- implementation 'com.android.support:design:28.0.0'
- implementation 'com.android.support.constraint:constraint-layout:1.1.3'
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.2'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
- }
Step 2
Now, we need two separate layouts. Put them under CoordinatorLayout. Let's see activity_main.xml.
- <android.support.design.widget.CoordinatorLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
-
- <include
- android:id="@+id/main_layout"
- layout="@layout/main_upper_layout" />
-
- <include
- android:id="@+id/bottom_sheet_lay"
- layout="@layout/lower_layout" />
-
- </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.
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="300dp"
- android:background="#D3D3D3">
-
-
- <TextView
- android:id="@+id/heading_textview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_alignParentTop="true"
- android:layout_marginTop="21dp"
- android:gravity="center"
- android:padding="20dp"
- android:text="This is upper layout show views of your choice." />
-
-
- <Button
- android:id="@+id/expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="106dp"
- android:text="Expand bottom sheet" />
-
- <Button
- android:id="@+id/collapse_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/expand_button"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="20dp"
- android:text="Collapse bottom sheet" />
-
- </RelativeLayout>
Now, we need the lower layout. So, let us observe lower_layout.xml.
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:padding="10dp"
- android:focusable="true"
- android:focusableInTouchMode="true"
- android:orientation="vertical"
- app:behavior_hideable="false"
- app:behavior_peekHeight="0dp"
- app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
- android:background="#ffffff">
-
- <TextView
- android:id="@+id/lower_sheet_textview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="40dp"
- android:text="Hi! this is bottom sheet layout scroll above to expand and scroll down to collapse." />
-
-
- <ImageView
- android:id="@+id/image_view"
- android:layout_width="25dp"
- android:layout_height="25dp"
- android:src="@drawable/ic_arrow_up_icon"
- android:layout_alignParentEnd="true"
- android:layout_centerHorizontal="true"
- android:layout_marginRight="10dp"/>
-
-
- <TextView
- android:id="@+id/textview1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="sample text 1"
- android:layout_marginTop="10dp"
- android:layout_below="@id/lower_sheet_textview"/>
- <TextView
- android:id="@+id/textview2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="sample text 2"
- android:layout_marginTop="10dp"
- android:layout_below="@id/textview1"/>
- <TextView
- android:id="@+id/textview3"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="sample text 3"
- android:layout_marginTop="10dp"
- android:layout_below="@id/textview2"/>
- <TextView
- android:id="@+id/textview4"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="sample text 4"
- android:layout_marginTop="10dp"
- android:layout_below="@id/textview3"/>
-
- </RelativeLayout>
Now, we are just taking the random height for both the layouts because we will set the height and peakHeight dynamically.
MainActivity.java
- import android.os.Bundle;
- import android.support.annotation.NonNull;
- import android.support.design.widget.BottomSheetBehavior;
- import android.support.v7.app.AppCompatActivity;
- import android.util.Log;
- import android.view.View;
- import android.widget.Button;
- import android.widget.ImageView;
- import android.widget.RelativeLayout;
-
-
- public class MainActivity extends AppCompatActivity implements View.OnClickListener {
-
- Button expandButton;
- Button collapseButton;
-
- RelativeLayout mainUpperLayout;
- RelativeLayout bottomSheetLayout;
- BottomSheetBehavior bottomSheetBehavior;
- ImageView expandCollapsedImgView;
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- expandCollapsedImgView = findViewById(R.id.image_view);
- mainUpperLayout = findViewById(R.id.main_layout);
- bottomSheetLayout = findViewById(R.id.bottom_sheet_lay);
- expandButton = findViewById(R.id.expand_button);
- expandButton.setOnClickListener(this);
-
- collapseButton = findViewById(R.id.collapse_button);
- collapseButton.setOnClickListener(this);
- setUp();
-
-
- }
-
- @Override
- public void onClick(View view) {
-
- switch (view.getId()) {
-
- case R.id.expand_button:
-
- BottomSheetBehavior.from(bottomSheetLayout)
- .setState(BottomSheetBehavior.STATE_EXPANDED);
- break;
-
- case R.id.collapse_button:
-
- BottomSheetBehavior.from(bottomSheetLayout)
- .setState(BottomSheetBehavior.STATE_COLLAPSED);
- break;
-
- default:
- break;
-
- }
- }
- private void setUp() {
-
- int totalAvailableHeight = (ScreenUtils.getScreenHeight(this) - ((ScreenUtils.getStatusBarHeight(this)) + ScreenUtils.getActionBarHeight(this)));
-
- Log.e("available screen height", String.valueOf(totalAvailableHeight));
-
- int upperHeight = totalAvailableHeight - 200;
-
- int bottomHeight = totalAvailableHeight - upperHeight;
-
- mainUpperLayout.getLayoutParams().height = upperHeight;
- mainUpperLayout.requestLayout();
-
- bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetLayout);
- bottomSheetBehavior.setPeekHeight(bottomHeight);
- bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
- @Override
- public void onStateChanged(@NonNull View bottomSheet, int newState) {
- switch (newState) {
- case BottomSheetBehavior.STATE_EXPANDED: {
-
- expandCollapsedImgView.setImageResource(R.drawable.ic_expand_arrow);
- }
- break;
- case BottomSheetBehavior.STATE_COLLAPSED: {
- expandCollapsedImgView.setImageResource(R.drawable.ic_arrow_up_icon);
-
- }
- break;
- }
- }
-
- @Override
- public void onSlide(@NonNull View bottomSheet, float slideOffset) {
-
- }
- });
- }
- }
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
- import android.content.Context;
- import android.util.DisplayMetrics;
- import android.util.TypedValue;
- import android.view.WindowManager;
-
-
- public class ScreenUtils {
-
- private ScreenUtils() {
-
- }
-
- public static int getScreenWidth(Context context) {
- WindowManager windowManager = (WindowManager) context
- .getSystemService(Context.WINDOW_SERVICE);
- DisplayMetrics dm = new DisplayMetrics();
- windowManager.getDefaultDisplay().getMetrics(dm);
- return dm.widthPixels;
- }
-
- public static int getScreenHeight(Context context) {
- WindowManager windowManager = (WindowManager) context
- .getSystemService(Context.WINDOW_SERVICE);
- DisplayMetrics dm = new DisplayMetrics();
- windowManager.getDefaultDisplay().getMetrics(dm);
- return dm.heightPixels;
- }
-
- public static int getStatusBarHeight(Context context) {
- int result = 0;
- int resourceId = context.getResources()
- .getIdentifier("status_bar_height", "dimen", "android");
- if (resourceId > 0) {
- result = context.getResources().getDimensionPixelSize(resourceId);
- }
- return result;
- }
-
- public static int getActionBarHeight(Context context){
- int actionBarHeight=0;
- TypedValue tv = new TypedValue();
- if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
- actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,context.getResources().getDisplayMetrics());
- }
- return actionBarHeight;
- }
- }
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
The 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 normally 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.
The 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.