Android BottomSheet Example in Kotlin

This article is about BottomSheet, types of BottomSheet, and how to integrate a basic Bottom Sheet in our Android application.

 

BottomSheet

BottomSheet is a component that is used to display some information by sliding the view up from the bottom of the screen and also, you can hide this BottomSheet when the message is conveyed to the users. This is a nice way of conveying some message or performing some task in Android.

Types of BottomSheet

BottomSheets is of two types:

Persistent BottomSheet:

  • The Persistent BottomSheet is visible along with other UI components on the screen.
  • Initially, some content of this Persistent BottomSheet is visible(you can set the peek height i.e. the initial height of BottomSheet to 0 also) and when you slide it up, then the rest of the content will appear along with the BottomSheet.

The following is an example of Persistent BottomSheet:

The bottom sheet contains location information over a map.
The bottom sheet contains media controls in a music app.

 

Modal BottomSheet: 

  • Modal bottom sheets present a set of choices while blocking interaction with the rest of the screen.
  • Unlike Persistent BottomSheet, it is totally invisible initially, and on certain actions (maybe a button click), it appears from the bottom of the screen.
  • It generally contains some list of items and the items of the list correspond to some action.
  • Modal bottom sheets are an alternative to inline menus or simple dialogs on mobile and provide room for additional items, longer descriptions, and iconography. 

The following is an example of Modal BottomSheet:

Use the bottom sheet to present additional screen actions.
bottom sheet to provide deep links to another app.

States of BottomSheet

Bottom sheets have 5 states and with the help of these states, we can perform various actions according to our needs.

  • STATE_COLLAPSED: This state indicates that the BottomSheet is collapsed i.e. the bottom sheet is visible up to its peek height only.
  • STATE_EXPANDED: This state indicates that the BottomSheet is fully expanded to its maximum height and all the content of the BottomSheet is visible.
  • STATE_DRAGGING: This state indicates that the BottomSheet is dragging either in the upward direction or in the downward direction.
  • STATE_SETTLING: This state indicates that the BottomSheet is settling either to the max height or to the peek height. (Sometimes height can be 0 also when behavior_hideable is set to true)
  • STATE_HIDDEN: This state indicates that the BottomSheet is hidden i.e. it is possible only when the value of behavior_hideable is set to true. Once, the BottomSheet is hidden, you can’t scroll it to make it visible. To make it visible again, you need to change its state by some action (maybe on some button click).

NOTE: These states are for Persistent BottomSheet.

Creating new project

  1. Create a new project by going to File  New Android Project, select Empty Activity, provide app name, select language to kotlin and then finally click on finish.
  2. Open app-level build.gradle file, add the dependency of material design, and sync the project.

build.gradle

dependencies {
    //material design
    implementation 'com.google.android.material:material:1.4.0'
}

Find the latest version of the material design from here.

Persistent BottomSheet Example

1 . The layout file layout_bottom_sheet.xml represents the UI of the Persistent BottomSheet. It consists of two text views, one for the title and one for the description.

layout_bottom_sheet.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/bottomSheet"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorPrimaryDark"
    app:behavior_hideable="false"
    app:behavior_peekHeight="40dp"
    app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:gravity="center_vertical"
        android:text="@string/title"
        android:textColor="@android:color/white"
        android:textSize="22sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tvSubtitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:text="@string/description"
        android:textColor="@android:color/white"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • com.google.android.material.bottomsheet.BottomSheetBehavior : defines the layout behavior.
  • behavior_hideable: used to set if the bottom sheet can be hidden totally when we drag it down or not.
  • behavior_peekHeight: This is the height up to which the bottom sheet will be visible in the collapsed state.

2. Add layout_bottom_sheet.xml layout to the activity_main.xml file. It consists of three buttons, one for Persistent BottomSheet, one for BottomSheetDialog, and one for BottomSheetDialogFragment.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/btnBottomSheet"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginStart="12dp"
        android:layout_marginEnd="12dp"
        android:text="Show Bottom Sheet" />

    <Button
        android:id="@+id/btnBottomSheetDialog"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginStart="12dp"
        android:layout_marginTop="60dp"
        android:layout_marginEnd="12dp"
        android:text="Show Bottom Sheet Dialog" />

    <Button
        android:id="@+id/btnBottomSheetDialogFragment"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginStart="12dp"
        android:layout_marginTop="120dp"
        android:layout_marginEnd="12dp"
        android:text="Show Bottom Sheet Dialog Fragment" />

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

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Note: BottomSheet is the direct child of the CoordinatorLayout.

3 . The MainActivity contains the following code:

MainActivity.kt

package com.c1ctech.androidbottomsheetexp

import android.os.Bundle
import android.view.View
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.layout_bottom_sheet.*


class MainActivity : AppCompatActivity() {
    private lateinit var bottomSheetBehavior: BottomSheetBehavior<ConstraintLayout>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)

        bottomSheetBehavior.addBottomSheetCallback(object :
            BottomSheetBehavior.BottomSheetCallback() {

            override fun onSlide(bottomSheet: View, slideOffset: Float) {
                // handle onSlide
            }

            override fun onStateChanged(bottomSheet: View, newState: Int) {
                when (newState) {
                    BottomSheetBehavior.STATE_COLLAPSED -> Toast.makeText(
                        this@MainActivity,
                        "STATE_COLLAPSED",
                        Toast.LENGTH_SHORT
                    ).show()
                    BottomSheetBehavior.STATE_EXPANDED -> Toast.makeText(
                        this@MainActivity,
                        "STATE_EXPANDED",
                        Toast.LENGTH_SHORT
                    ).show()
                    BottomSheetBehavior.STATE_DRAGGING -> Toast.makeText(
                        this@MainActivity,
                        "STATE_DRAGGING",
                        Toast.LENGTH_SHORT
                    ).show()
                    BottomSheetBehavior.STATE_SETTLING -> Toast.makeText(
                        this@MainActivity,
                        "STATE_SETTLING",
                        Toast.LENGTH_SHORT
                    ).show()
                    BottomSheetBehavior.STATE_HIDDEN -> Toast.makeText(
                        this@MainActivity,
                        "STATE_HIDDEN",
                        Toast.LENGTH_SHORT
                    ).show()
                    else -> Toast.makeText(this@MainActivity, "OTHER_STATE", Toast.LENGTH_SHORT)
                        .show()
                }
            }
        })

        btnBottomSheet.setOnClickListener {
            if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
                bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED

            } else {
                bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED

            }
        }
    }
}
  • onStateChanged(): override to observe various states of the BottomSheet.

Here, On click of the button, we are changing its state i.e. when it is expanded then we are collapsing it and vice-versa.

4. Run the app, the bottom sheet displayed at the bottom. Observe the various states of the BottomSheet.

             

 

Modal BottomSheet Example using BottomSheetDialog

1 . The layout file layout_bottom_sheet_dialog.xml represents the UI of the Modal BottomSheet creating using BottomSheetDialog.

layout_bottom_sheet_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
    android:padding="10dp">

    <RelativeLayout
        android:id="@+id/rl_edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageButton
            android:id="@+id/imgEdit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true"
            android:layout_marginStart="6dp"
            android:background="@android:color/transparent"
            android:src="@drawable/ic_edit" />

        <TextView
            android:id="@+id/tv_edit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginStart="6dp"
            android:layout_toRightOf="@+id/imgEdit"
            android:text="Edit" />

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl_delete"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:padding="8dp"
        app:layout_constraintEnd_toEndOf="@+id/rl_edit"
        app:layout_constraintStart_toStartOf="@+id/rl_edit"
        app:layout_constraintTop_toBottomOf="@+id/rl_edit">


        <ImageButton
            android:id="@+id/imgDelete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true"
            android:layout_marginStart="6dp"
            android:background="@android:color/transparent"
            android:src="@drawable/ic_delete" />

        <TextView
            android:id="@+id/tv_delete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginStart="6dp"
            android:layout_toRightOf="@+id/imgDelete"
            android:text="Delete" />

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl_add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:padding="8dp"
        app:layout_constraintEnd_toEndOf="@+id/rl_delete"
        app:layout_constraintStart_toStartOf="@+id/rl_delete"
        app:layout_constraintTop_toBottomOf="@+id/rl_delete">

        <ImageButton
            android:id="@+id/imgAdd"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true"
            android:layout_marginStart="6dp"
            android:background="@android:color/transparent"
            android:src="@drawable/ic_add" />

        <TextView
            android:id="@+id/tv_add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginStart="6dp"
            android:layout_toRightOf="@+id/imgAdd"
            android:text="Add" />

    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

2 . In MainActivity, add the below function which is invoked on BottomSheetDialog button click.

//setting clicklistener
btnBottomSheetDialog.setOnClickListener { showBottomSheetDialog() } private fun showBottomSheetDialog() { val dialog = BottomSheetDialog(this) dialog.setContentView(R.layout.layout_bottom_sheet_dialog) val RLEdit = dialog.findViewById<RelativeLayout>(R.id.rl_edit) val RLDelete = dialog.findViewById<RelativeLayout>(R.id.rl_delete) val RLAdd = dialog.findViewById<RelativeLayout>(R.id.rl_add) RLEdit?.setOnClickListener { //handle click event Toast.makeText(this, "Perform edit operation", Toast.LENGTH_SHORT).show() } RLDelete?.setOnClickListener { //handle click event Toast.makeText(this, "Perform delete operation", Toast.LENGTH_SHORT).show() } RLAdd?.setOnClickListener { //handle click event Toast.makeText(this, "Perform add operation", Toast.LENGTH_SHORT).show() } dialog.show() }

Here, after creating BottomSheetDialog instance:

  • set dialog layout using setContentView().
  • get dialog views by its id and set click listeners on it.

3 . Run the app, the bottom sheet is displayed at the bottom of the screen.     

 

Modal BottomSheet Example using BottomSheetDialogFragment

1 . Create a subclass of BottomSheetDialogFragment under the root directory, right-click > New > Kotlin File/Class, and name it as BottomSheetFragment.

BottomSheetFragment.kt

package com.c1ctech.androidbottomsheetexp

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.layout_bottom_sheet_dialog.*

class BottomSheetFragment : BottomSheetDialogFragment() {

    companion object {

        const val TAG = "CustomBottomSheetDialogFragment"

    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.layout_bottom_sheet_dialog, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        rl_edit.setOnClickListener {
            //handle click event
            Toast.makeText(context, "Perform edit operation", Toast.LENGTH_SHORT).show()
        }
        rl_delete.setOnClickListener {
            //handle click event
            Toast.makeText(context, "Perform delete operation", Toast.LENGTH_SHORT).show()
        }
        rl_add.setOnClickListener {
            //handle click event
            Toast.makeText(context, "Perform add operation", Toast.LENGTH_SHORT).show()
        }
    }
}

Here, we will inflate the layout and handle the button clicks of the inflated layout elements.

2 . In MainActivity, On click of Button (btnBottomSheetDialogFragment ), we will call the DialogFragment show() method which will add the fragment to the fragmentManager and display the dialog.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
       
       //handle click listener
        btnBottomSheetDialogFragment.setOnClickListener {
            showBottomSheetDialogFragment()
        }
    }

    //Display the dialog,adding the fragment to the fragmentManager
    private fun showBottomSheetDialogFragment() {
        BottomSheetFragment().show(getSupportFragmentManager(), BottomSheetFragment.TAG);
    }
  }

Leave a Reply