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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<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
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<?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="https://www.journaldev.com/13086/@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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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.