Android Bottom Sheet

Filed Under: Android

In this tutorial we’ll discuss and implement android Bottom Sheet widget that was introduced with Android Support v23.2.

Android Bottom Sheet

According to google material design documentation;

A bottom sheet is a sheet that slides up from the bottom edge of the screen. Bottom sheets are displayed as a result of user triggered action, and also it can reveal additional content by swiping up.

Bottom sheet can be either modal – that slides up from bottom of the screen to reveal more content or persistent – when they’re integrated with the app to display supporting content.

BottomSheets can be implemented as BottomSheetBehavior, BottomSheetDialog and BottomSheetDialogFragment.

Android BottomSheetBehavior

BottomSheetBehavior is a type of layout_behavior used for persistent bottom sheets. It requires setting CoordinatorLayout as the root element of that layout and adding the xml attribute app:layout_behavior:android.support.design.widget.BottomSheetBehavior to the child view.

Let’s look at a sample xml child view that’ll be put inside the CoordinatorLayout.


<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        app:behavior_hideable="false"
        app:behavior_peekHeight="120dp"
        android:orientation="vertical"
        app:layout_behavior="@string/bottom_sheet_behavior">


        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/lorem_ipsum"/>

    </LinearLayout>

Few inferences drawn from the above code snippet are:

  1. layout_behavior sets the view as bottom sheet
  2. behavior_peekHeight sets the visible part of the sheet
  3. behavior_hideable sets whether the view can be hidden by dragging it further downwards. It accepts boolean values.

Let’s create a new Project in Android Studio, set the template to Basic Activity and add the above the xml snippet in the activity_main.xml.


<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.journaldev.bottomsheet.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

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

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

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email" />


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        app:behavior_hideable="false"
        app:behavior_peekHeight="120dp"
        android:orientation="vertical"
        app:layout_behavior="@string/bottom_sheet_behavior">


        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/lorem_ipsum"/>

    </LinearLayout>


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

Let’s run our application once and see how it behaves.

android bottom sheet

Set app:behavior_hideable=”true” and the bottom sheet would stay hidden once dragged down.

android bottom sheet example

The BottomSheetBehavior class allows us to set the present state of the view programmatically. Following are the important constants used to handle the state:

  1. STATE_COLLAPSED : Sets the bottom sheet height with the value set on the peekHeight attribute.
  2. STATE_DRAGGING : The bottom sheet is being dragged
  3. STATE_EXPANDED : The bottom sheet is completely expanded
  4. STATE_HIDDEN : The bottom sheet is completely hidden from the screen

Let’s jump onto the business end of this tutorial. We’ll develop an application that displays a RecyclerView inside the bottom sheet with items to select.

Android Bottom Sheet Example Project Structure

android bottom sheet example project structure

Android Bottom Sheet Example Code

The activity_main.xml layout is defined below.


<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:id="@+id/coordinatorLayout"
    tools:context="com.journaldev.bottomsheet.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

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

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


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:orientation="vertical"
        app:behavior_hideable="true"
        app:behavior_peekHeight="0dp"
        android:id="@+id/bottom_sheet"
        android:background="@android:color/white"
        app:layout_behavior="@string/bottom_sheet_behavior">


        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center"
            android:textStyle="bold"
            android:text="SELECT AN ITEM"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:layout_marginTop="16dp" />

    </LinearLayout>


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

MainActivity.java is given below.


package com.journaldev.bottomsheet;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity implements ItemAdapter.ItemListener {


    BottomSheetBehavior behavior;
    RecyclerView recyclerView;
    private ItemAdapter mAdapter;
    CoordinatorLayout coordinatorLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinatorLayout);


        View bottomSheet = findViewById(R.id.bottom_sheet);
        behavior = BottomSheetBehavior.from(bottomSheet);
        behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {
                // React to state change
            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                // React to dragging events
            }
        });

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        ArrayList items = new ArrayList();
        items.add("Item 1");
        items.add("Item 2");
        items.add("Item 3");
        items.add("Item 4");
        items.add("Item 5");
        items.add("Item 6");

        mAdapter = new ItemAdapter(items, this);
        recyclerView.setAdapter(mAdapter);

        Button button = (Button) findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

    }

    @Override
    public void onItemClick(String item) {

        Snackbar.make(coordinatorLayout,item + " is selected", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show();

        behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);

    }
}

Following are the inferences drawn from the above code.

behavior = BottomSheetBehavior.from(bottomSheet);
The from(View view) is a static method of the BottomSheetBehavior class and is used to take the instance of the behavior from the layout params of the View instance.

BottomSheetCallback is called upon the BottomSheetBehavior instance for us to receive callbacks like state changes and offset changes for our bottom sheet.

The ItemAdapter.java class has the implementation for the adapter of the RecyclerView as shown below.


package com.journaldev.bottomsheet;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

class ItemAdapter extends RecyclerView.Adapter {

    private List mItems;
    private ItemListener mListener;

    ItemAdapter(List items, ItemListener listener) {
        mItems = items;
        mListener = listener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.bottom_sheet_item, parent, false));
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.setData(mItems.get(position));
    }

    @Override
    public int getItemCount() {
        return mItems.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        TextView textView;
        String item;

        ViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(this);
            textView = (TextView) itemView.findViewById(R.id.textView);
        }

        void setData(String item) {
            this.item = item;
            textView.setText(item);
        }

        @Override
        public void onClick(View v) {
            if (mListener != null) {
                mListener.onItemClick(item);
            }
        }
    }

    interface ItemListener {
        void onItemClick(String item);
    }
}

The above code is similar to the ones we’ve been implementing in the previous RecyclerView tutorials barring the interface you see.

The interface is implemented in the MainActivity.java class with the aim to make the onItemClick functionality of RecyclerView similar to a ListView.

Back in the MainActivity.java class,

behavior.setState(BottomSheetBehavior.STATE_EXPANDED); expands the Bottom Sheet. Clicking a RecyclerView item would display a SnackBar and collapse the Bottom Sheet.

Let’s look at the application in action.

android bottom sheet example application

In the above output we can see that the BottomSheet can be dragged down without selecting an item.
To prevent it from collapsing without an item been selected, set the BottomSheetCallBack as:


behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {
                if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                    behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                }
            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                // React to dragging events
            }
        });

This brings an end to android bottom sheet tutorial. We’ve implemented the persistent android Bottom Sheet in our application. We’ll delve into modal bottom sheets in a later tutorial. You can download the Android BottomSheet Tutorial from the link below.

Comments

  1. Lingeshwaran N says:

    Recreated view in recyclerview is not clicking in the first click. Second time only can able to click

  2. Anas Ahmed says:

    I have two recyclerviews in bottom sheet one is vertical and 2nd in horizontal. horizontal works fine vertical not scrolling. i make the horizontal gone for testing but still vertical not scroll. if i put both recyclerviews in Nested Scroll both scrolls fine but i don’t want horizontal Recyclerview to scroll vertical.

  3. Brian Hong says:

    I spent some hours for making the onClick event inside recyclerview work.
    I thank this tutorial’s author, and here I leave the solution:

    Don’t do this:
    class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    but do this:
    class ViewHolder extends RecyclerView.ViewHolder {

    and then, instead of onClick() inside class ViewHolder, do this:

    ViewHolder(View itemView) {
    super(itemView);
    itemView.setOnClickListener(this);
    textView = (TextView) itemView.findViewById(R.id.textView);

    itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

    if (listener != null) {
    listener.onItemClick(…);
    }
    }
    }

  4. Gentle says:

    I have lots of data in the recyclerview but it is not scrolling inside bottom sheet

    1. Anupam says:

      A BottomSheet supports only single scrolling child. Make sure you don’t have multiple root child views.
      Besides a BottomSheet is scrollable, so you need to disable it to make the RecyclerView scroll.
      That means you can no longer drag the BottomSheet.

      final BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
              behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
                  @Override
                  public void onStateChanged(@NonNull View bottomSheet, int newState) {
                      if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                          behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                      }
                  }
      
                  @Override
                  public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                  }
              });
      
      fab.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      if (behavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
                          behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                      } else {
                          behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                      }
                  }
              });
      
      

      Don’t use peekHeight attribute.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages