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;
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:
- layout_behavior sets the view as bottom sheet
- behavior_peekHeight sets the visible part of the sheet
- 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="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://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.
Set app:behavior_hideable=”true” and the bottom sheet would stay hidden once dragged down.
The BottomSheetBehavior
class allows us to set the present state of the view programmatically. Following are the important constants used to handle the state:
- STATE_COLLAPSED : Sets the bottom sheet height with the value set on the peekHeight attribute.
- STATE_DRAGGING : The bottom sheet is being dragged
- STATE_EXPANDED : The bottom sheet is completely expanded
- 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 Code
The activity_main.xml
layout is defined below.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://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.
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.
hi, bottom sheet setBottomSheetCallback method is deprecated so how to use removeBottomSheetCallback() and addBottomSheetCallback()
Just replace
setBottomSheetCallback
withaddBottomSheetCallback
and implement the methods. Most of it is same.Recreated view in recyclerview is not clicking in the first click. Second time only can able to click
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.
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(…);
}
}
}
I have lots of data in the recyclerview but it is not scrolling inside bottom sheet
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.
Don’t use peekHeight attribute.