Implementing Bottom Sheet Dialogs using Android Studio
Bottom Sheet dialogs seem to be replacing regular Android dialogs and menus. The Bottom Sheet is a component that slides up from the bottom of the screen to showcase additional content in your application. <!--more--> A Bottom Sheet dialog is like a message box triggered by the user's actions. Companies such as Google, Facebook, and Twitter have implemented this feature in their applications.
Bottom Sheet dialogs are used in music, payment, and file management applications. In terms of application, any Android view
including TextView
, ImageView
, RecyclerViews
, Buttons
, and Text inputs
can be included in a Bottom Sheet. This makes it quite dynamic.
For instance, it can help display data retrieved from a database. A Bottom Sheet can be applied in many instances as long as it fits your application cycle.
Types of the Bottom Sheet dialogs
The two main types of Bottom Sheets are Modal and Persistent dialogs;
Modal Bottom Sheet dialog
It has similar characteristics as an Alert dialog. When triggered (by the user’s action), it slides up from the bottom of the current screen. A Modal Sheet can contain a list of items.
These elements can correspond to some action when clicked. The Modal Bottom Sheet blocks interaction with the rest of the screen to indicate a shift of focus. It is dismissed when the user clicks outside the view or on back press.
Instead of wrapping the Modal Bottom Sheet with the CoordinatorLayout
like the persistent dialog, we create it dynamically, just like a regular dialog.
An excellent example of a Modal Bottom Sheet dialog is the Google Drive application.
Or this payment Bottom Sheet dialog example.
.
Modal Bottom Sheets are an excellent alternative to inline menus and simple dialogs. They provide additional room for more content, iconography, and more screen actions.
Persistent Bottom Sheet dialog
Persistent Bottom Sheet dialogs provide supplementary content about the current screen. It is as a child of the CoordinatorLayout
.
A portion of the container is visible to provide users with more content. Unlike the Modal dialog, a Persistent Bottom Sheet widget is permanent for the current screen content.
Here is an example of a Persistent Bottom Sheet dialog in a Google Maps application.
Implementation
Create a new Android studio project. To implement a Bottom Sheet dialog, you need a material design library.
Include the following library in your app.gradle
file.
implementation 'com.google.android.material:material:1.2.1'
Sync the project to download the library. This will make all the required functions available in your project.
We will discuss how to implement the two types of Bottom Sheet dialogs using Android studio.
Implementing a Modal Bottom Sheet dialog
- Using BottomsheetDialog
Preparing layouts
To show the dialog, you need an XML file that arranges the dialog's content. You can choose to use any widgets that fit in the dialog. The views can include RecyclerView
, ImageViews
, Text
, Inputs
, and Button
.
Make sure you generate some vector images as showcased in the ImageView of the below XML file. Or check this GitHub repository for more reference.
Here is the bottom_sheet_dialog_layout.xml
layout that I will be using to implement a Modal Bottom Sheet.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/download"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_cloud_download_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Download File"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/shareLinearLayout"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_share_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Share"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/uploadLinearLaySout"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_add_to_drive_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Upload To Google Drive"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/copyLinearLayout"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_file_copy_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Copy Link"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/delete"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_delete_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete File Selection"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
</LinearLayout>
A dialog is triggered by a specified user action. In this tutorial, we will include a button and trigger the dialog using it's onClick
Listener.
Go ahead and add a button in your activity_main.xml
file.
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button"
android:backgroundTint="@color/purple_500"
android:fontFamily="serif"
android:text="Show Dialog"
android:textColor="#FFFFFF"
android:textSize="18sp"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="116dp"
tools:layout_editor_absoluteY="28dp"/>
Initializing the Bottom Sheet in the activity
Initialize the button and set the onClick Listener inside the onCreate
function. When the button is clicked, we will show the dialog. Create a function showBottomSheetDialog()
and call it inside the button's onClick
Listener, as shown below.
Button mBottton = findViewById(R.id.button);
mBottton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showBottomSheetDialog()
}
});
Inside the showBottomSheetDialog()
function, initialize the Bottom Sheet dialog. Initialize the bottom_sheet_dialog_layout.xml
using the setContentView
method.
Declare all the views and call them by id
as specified in the Bottom Sheet layout. Finally, we will diplay the dialog using bottomSheetDialog.show()
.
private void showBottomSheetDialog() {
final BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this);
bottomSheetDialog.setContentView(R.layout.bottom_sheet_dialog);
LinearLayout copy = bottomSheetDialog.findViewById(R.id.copyLinearLayout);
LinearLayout share = bottomSheetDialog.findViewById(R.id.shareLinearLayout);
LinearLayout upload = bottomSheetDialog.findViewById(R.id.uploadLinearLayout);
LinearLayout download = bottomSheetDialog.findViewById(R.id.download);
LinearLayout delete = bottomSheetDialog.findViewById(R.id.delete);
bottomSheetDialog.show();
}
Run the app to test if the Bottom Sheet is working. Clicking the button should trigger the dialog to slide from the bottom to the top.
Set onClick Listener
Each element in the dialog layout can be assigned an action. When an item is clicked, it will redirect the user as specified in the code.
In our application, we will show a Toast message when an element is clicked. You can make modifications in your future apps to direct users to different activities.
Add the following OnClickListeners
right above bottomSheetDialog.show()
.
copy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Copy is Clicked ", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
share.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Share is Clicked", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
upload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Upload is Clicked", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Download is Clicked", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Delete is Clicked", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
We use bottomSheetDialog.dismiss()
to close the dialog once an element is clicked.
You can also set a more distinct action, instructing your application to do something when the dialog is dismissed. For instance, the app can launch a new activity.
bottomSheetDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
// Instructions on bottomSheetDialog Dismiss
}
});
Testing the application
- BottomSheetDialogFragment
A fragment can be displayed as a Bottom Sheet dialog. Go ahead and create a new fragment, call it
BottomSheetFragment
. You can opt to start a new project.
Creating a new fragment will generate an XML file associated with it. Go ahead and include your layout design in it. Use the same layout as specified in bottom_sheet_dialog_layout.xml
. Inflate the layout for this fragment, as shown below.
public class BottomSheetFragment extends Fragment {
public BlankFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_dialog, container, false);
return view;
}
}
Add a button in the activity_main.xml
, declare it, and set OnClick
Listener as dicussed in the previous steps.
We want to open the fragment when the button is clicked.
Indicate the following code block inside the button's OnClick
Listener.
Button bottton = findViewById(R.id.button);
bottton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BlankFragment blankFragment = new BlankFragment();
blankFragment.show(getSupportFragmentManager(),blankFragment.getTag());
}
});
We need to convert the fragment to a Bottom Sheet. We do so by extending BottomSheetDialogFragment
rather than Fragment
.
public class BottomSheetFragment extends BottomSheetDialogFragment {
}
When you run the application, it should show the dialog shown below.
Check the code used to implement both Modal dialogs on GitHub.
Implementing a Persistent Bottom Sheet dialog
Laying out the Bottom Sheet design
We will use an example of a simple login screen. Instead of showing it within the regular activity layout, we will use a Persistent dialog to slide it into the main screen.
I have created a bottom_sheet_dialog_layout.xml
file and included the following simple login layout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_sheet_layout"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/bottom_sheet_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/purple_500"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="3"
android:fontFamily="serif"
android:padding="24dp"
android:textSize="18sp"
android:text="Welcome Back. Please Login"
android:textColor="@android:color/white" />
<ImageView
android:id="@+id/bottom_sheet_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
app:srcCompat="@drawable/ic_baseline_keyboard_arrow_up_24" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="@color/teal_200"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:filterTouchesWhenObscured="false"
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="serif"
android:gravity="center_horizontal"
android:text="login"
android:textSize="36sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor= "@color/purple_500"
android:fontFamily="serif"
android:text="username"
android:textSize="24sp" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:fontFamily="serif"
android:hint="enter_email_address"
android:inputType="textEmailAddress" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor= "@color/purple_500"
android:fontFamily="serif"
android:text="password"
android:textSize="24sp" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:fontFamily="serif"
android:hint="enter_password"
android:inputType="textPassword" />
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="50dp"
android:fontFamily="serif"
android:text="login"
android:backgroundTint="@color/purple_500"
android:textColor="#FFFFFF"
android:textSize="18sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
This is not a Bottom Sheet yet. It's just a regular layout. To transform the layout to a Bottom Sheet dialog, a few declarations should be added to the root layout.
These statements will control the Bottom Sheet's behaviors. You can learn more about these attributes from here.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_sheet_layout"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content"
app:behavior_hideable="false"
app:behavior_peekHeight="62dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
The Bottom Sheet behavioral flags include;
-
app:layout_behavior
- applies theBottomSheetBehavior
into the XML file. This is assigned tocom.google.android.material.bottomsheet
. It is the most importantBottomSheetBehavior
attribute since it defines a given layout as a Bottom Sheet dialog. -
app:behavior_hideable
- takes a Boolean value. Iftrue
, a user can drag and hide the dialog by sliding it down. If false, the dialog will float on the screen and will not be hideable. -
app:behavior_peekHeight
- it defines the height of the Bottom Sheet visible to the user.
Remember to add an id to be used to access the layout.
For a Bottom Sheet to be implemented effectively, it must be a child of CoordinatorLayout
. To do that, go to your main XML file. This could be an Activity or Fragment. In our case, it will be the activity_main.xml
.
Here is the code to do that.
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/bottom_sheet_dialog_layout" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Remember to include the Bottom Sheet we designed. Wrap it with CoordinatorLayout
.
Expanding and collapsing the sheet dialog
To control the sliding and collapsing of the dialog, we use states. The Bottom Sheet has several states which you need to understand. They include:
STATE_EXPANDED
- the dialog is visible to its maximum defined height.STATE_COLLAPSED
- the dialog is visible depending on the setpeekHeight
.STATE_DRAGGING
- the user is dragging the dialog up and down.STATE_SETTLING
- show that the dialog is settling at a specific height. This can be thepeekHeight
, expanded height, or zero if the dialog is hidden.STATE_HIDDEN
- the dialog is not visible.
The last thing we will do is listen to the state of the dialog. We use BottomSheetCallback
to detect any state changes.
Declare the following parameters:
private LinearLayout mBottomSheetLayout;
private BottomSheetBehavior sheetBehavior;
private ImageView header_Arrow_Image;
Initialize the behavior Bottom Sheet layout and the arrow image:
mBottomSheetLayout = findViewById(R.id.bottom_sheet_layout);
sheetBehavior = BottomSheetBehavior.from(mBottomSheetLayout);
header_Arrow_Image = findViewById(R.id.bottom_sheet_arrow);
We will assign OnClick
Listener to the arrow
vector image. When clicked, we want to expand or collapse the dialog.
header_Arrow_Image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(sheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED){
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else {
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
});
Implement a BottomSheetCallback
to listen to the BottomSheetBehavior
state.
sheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
header_Arrow_Image.setRotation(slideOffset * 180);
}
});
onStateChanged
tells the application what's happening on the dialog depending on the state. onSlide
will rotate the arrow image (while sliding bottom to top) until the STATE_EXPANDED
has reached its maximum height.
On the other side, the image will rotate to its original state when STATE_COLLAPSED
is at peekHeight
.
Run the application
Check the code used to implement the Persistent dialog on GitHub.
Conclusion
The Bottom Sheet dialog is a unique way to display menus and dialogs. It provides more room to include content. Bottom Sheet dialogs can accomodate different components.
Check out Material documentation to learn more about the Bottom Sheet dialog.
Happy coding.
Peer Review Contributions by Wanja Mike