Bottom Sheets in Android to show dialogs with Android Studio

- Andrés Cruz

En español

A Bottom Sheets is nothing more than a section or sliding panel, just like the Drawer is, therefore now we have a new sliding panel to easily add elements or expand functionalities of our application; this not-so-new component has come from version 23.2 of the Android support library.

There are two variants of bottom sheets or sliding panels/modals that we can use in Android; on the one hand we have the Persistent Bottom Sheet, which are used to show information of the same level or complementary to that shown in the view that contains said modal. These panels have the same elevation as the rest of the view and remain visible even when not in use.

The second way that we have are the modal bottom sheet, which are used as an alternative to other elements that Android offers us such as menus, dialogs, alerts, etc; these have a higher elevation than the main content; which means that they are obscured when said panel or modal is displayed, and must be hidden in order to continue using it with the rest of the application; they work as a lock screen.

This sliding panel called Bottom Sheets has five statuses; In general, each one of the statuses is quite descriptive, but even so, we will add a small description to each one of them; the first one we will see will be the persistent bottom sheet, which as we defined is a panel that has the same elevation as that of the content, therefore the content that is "behind" it is not obscured:

Bottom Sheets States in Android

Bottom Sheets have statuses as we can see below that allow us to know the status of a Bottom Sheet at any given time:

  • BottomSheetBehavior.STATE_COLLAPSED: The Bottom Sheets is collapsed, which means the Bottom Sheets is fully open.
  • BottomSheetBehavior.STATE_DRAGGINGThe Bottom Sheets is being dragged by the user.
  • BottomSheetBehavior.STATE_EXPANDED: The Bottom Sheets is not expanded (STATE_COLLAPSED), but it is not totally hidden either (STATE_HIDDEN). A section of it is shown to the "bottom" of the activity whose size is defined from the behavior_peekHeight property.
  • BottomSheetBehavior.STATE_HIDDENThe Bottom Sheets is completely hidden and will not be shown unless we indicate through an action that it be shown (ie from Java code we indicate that at least a portion of the Bottom Sheets is shown so that the user can manipulate it).
  • BottomSheetBehavior.STATE_SETTLING Like BottomSheetBehavior.STATE_DRAGGING, this is a transient event, which means that it is executed when the Bottom Sheets is scrolling between previous events.

Of which, in my opinion, the most important or the ones that could be used the most are these:

  • BottomSheetBehavior.STATE_COLLAPSED:
  • BottomSheetBehavior.STATE_EXPANDED

Which were explained previously and you can see exemplified in this image:

Bottom sheets ejemplo en android

How can you tell, when the panel or Bottom Sheets is fully open, the BottomSheetBehavior.STATE_EXPANDED state is set, now if it is folded the BottomSheetBehavior.STATE_COLLAPSED state is set.

In the case of BottomSheetBehavior.STATE_COLLAPSED, a small section of the Bottom Sheets or panel is shown, while with BottomSheetBehavior.STATE_EXPANDED the entire layout that we define is shown, although with the event system we can easily hide/show layout or perform any other operation.

How practically all the elements in Android are customizable, therefore we can add any element or quantity of them as well as a design and custom events contained within the panel; to do things like a custom player like the one shown in the presentation image of this post.

Creating the Bottom Sheets in Android

With the above clear, we open our Android Studio and as a first step, we have to add the corresponding dependencies; You must check with the current version in which this Android is; but the idea is the following; add the build.gradle file with the following dependencies:

dependencies {
    ...
implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
implementation 'com.android.support:design:28.0.0-alpha1'
}

Once the above is done, we go to the layout of our activity and add the following code:

<?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="samples.despotoski.nikola.com.bottomsheetsample.MainActivity">

   <android.support.design.widget.AppBarLayout
       android:id="@+id/app_bar"
       android:layout_width="match_parent"
       android:layout_height="@dimen/app_bar_height"
       android:fitsSystemWindows="true"
       android:theme="@style/AppTheme.AppBarOverlay">

       <android.support.design.widget.CollapsingToolbarLayout
           android:id="@+id/toolbar_layout"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:fitsSystemWindows="true"
           app:contentScrim="?attr/colorPrimary"
           app:layout_scrollFlags="scroll|exitUntilCollapsed">

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

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

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

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

As you can see, we add the Bottom Sheets code through an include, although you can include the code directly if you prefer; finally, our layout of our Bottom Sheets which will look as follows:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@+id/ll_bottom_sheet"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="@android:color/white"
   android:orientation="vertical"
   app:behavior_peekHeight="60dp"
   app:layout_behavior="@string/bottom_sheet_behavior">

   <TextView
       android:layout_width="match_parent"
       android:layout_height="60dp"
       android:background="#CCCCCC"
       android:gravity="center"
       android:text="@string/arrastrar"
       android:textAppearance="?android:attr/textAppearanceLarge" />

   <TextView
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:gravity="center"
       android:text="@string/contenido"
       android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>

As you can see there are two "strange" attributes which are:

  • app:layout_behavior="@string/bottom_sheet_behavior" with the app:layout_behavior attribute with the value @string/bottom_sheet_behavior turns our LinearLayout or another container we choose into a bottom sheet.
  • app:behavior_peekHeight="60dp" With the app:behavior_peekHeight attribute we set the size of the section that will be displayed in the BottomSheetBehavior.STATE_COLLAPSED state.

If we do not want the Bottom Sheets to occupy the entire height of the container, we simply have to vary the android:layout_height attribute of the parent container of the panel, which in our example is the container with the attribute android:id="@+id/ll_bottom_sheet".

Java code for the Bottom Sheets in Android

Now we are going to see some Android code which is necessary (the minimum) to tell Android to create the Bottom Sheets from the previous layouts; we declare a variable of type BottomSheetBehavior:

private BottomSheetBehavior bottomSheetBehavior;

Now we simply reference our view and with the from() method we create the bottom sheet; that simple:

LinearLayout linearLayout = (LinearLayout) findViewById(R.id.ll_bottom_sheet); bottomSheetBehavior = BottomSheetBehavior.from(linearLayout);

Android Bottom Sheets Events

Regarding events, we can execute custom sections of code based on the state of the panel with code like the following:

bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
   @Override
   public void onStateChanged(@NonNULL View bottomSheet, int newState) {
       switch (newState) {
           case BottomSheetBehavior.STATE_EXPANDED:
               Log.i("BottomSheetBehavior", "STATE_EXPANDED");
               break;
           case BottomSheetBehavior.STATE_DRAGGING:
               Log.i("BottomSheetBehavior", "STATE_DRAGGING");
               break;
           case BottomSheetBehavior.STATE_COLLAPSED:
               Log.i("BottomSheetBehavior", "STATE_COLLAPSED");
               break;
           case BottomSheetBehavior.STATE_HIDDEN:
               Log.i("BottomSheetBehavior", "STATE_HIDDEN");
               break;
           case BottomSheetBehavior.STATE_SETTLING:
               Log.i("BottomSheetBehavior", "STATE_SETTLING");
               break;
       }
   }

   @Override
   public void onSlide(@NonNULL View bottomSheet, float slideOffset) {
   }
});

Their states have already been explained above; finally the complete code of our main activity is:

public class MainActivity extends AppCompatActivity {

   private BottomSheetBehavior bottomSheetBehavior;

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

       Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
       setSupportActionBar(toolbar);
       LinearLayout linearLayout = (LinearLayout) findViewById(R.id.ll_bottom_sheet);

       bottomSheetBehavior = BottomSheetBehavior.from(linearLayout);
       bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
           @Override
           public void onStateChanged(@NonNULL View bottomSheet, int newState) {
               switch (newState) {
                   case BottomSheetBehavior.STATE_EXPANDED:
                       Log.i("BottomSheetBehavior", "STATE_EXPANDED");
                       break;
                   case BottomSheetBehavior.STATE_DRAGGING:
                       Log.i("BottomSheetBehavior", "STATE_DRAGGING");
                       break;
                   case BottomSheetBehavior.STATE_COLLAPSED:
                       Log.i("BottomSheetBehavior", "STATE_COLLAPSED");
                       break;
                   case BottomSheetBehavior.STATE_HIDDEN:
                       Log.i("BottomSheetBehavior", "STATE_HIDDEN");
                       break;
                   case BottomSheetBehavior.STATE_SETTLING:
                       Log.i("BottomSheetBehavior", "STATE_SETTLING");
                       break;
               }
           }

           @Override
           public void onSlide(@NonNULL View bottomSheet, float slideOffset) {
           }
       });
   }

   @Override
   public void onBackPressed() {
       if (bottomSheetBehavior.getState() != BottomSheetBehavior.STATE_HIDDEN) {
           bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
       } else {
           super.onBackPressed();
       }
   }

   @Override
   public boolean onCreateOptionsMenu(Menu menu) {

       getMenuInflater().inflate(R.menu.menu_bottom_sheet, menu);
       return true;
   }

   @Override
   public boolean onOptionsItemSelected(MenuItem item) {
       int id = item.getItemId();

       if (id == R.id.action_settings) {
           return true;
       }
       return super.onOptionsItemSelected(item);
   }
}

And with this we get a persistent Bottom Sheets like the following:

persistent bottom sheet

Creating a modal bottom sheet in Android Studio

If, on the other hand, you wanted to use the bottom sheet modal, which is a modal that overlaps or is positioned above the rest of the content and therefore a gray shadow appears over the rest of the content:

modal bottom sheet

We must create a class that extends BottomSheetDialogFragment that defines said modal; which in the end is a modal fragment and from here you can make the connections to the database, to some API, validate internally in the app, etc; a simple scheme would be the following:

public class MiBottomSheetDialogFragment extends BottomSheetDialogFragment {

   static MiBottomSheetDialogFragment newInstance() {
       return new MiBottomSheetDialogFragment();
   }

   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       View v = inflater.inflate(R.layout.bottom_sheet_modal_fragment, container, false);
       return v;
   }
}

In the layout, you can use the one presented above, or the one you prefer; finally, in the activity that we want to show the Modal Bottom Sheet we do the following:

        Button b_open_bs = (Button)findViewById(R.id.b_open_bs);
        b_open_bs.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                BottomSheetDialogFragment bsdFragment =
                        MiBottomSheetDialogFragment.newInstance();
                bsdFragment.show(
                        MainActivity.this.getSupportFragmentManager(), "BSDialog");
            }
        });
Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy