More details, a bottom sheet is a sheet that slides up from the bottom edge of the screen. Bottom sheets are displayed only as a result of a user-initiated action, and can be swiped up to reveal additional content. A bottom sheet can be a temporary modal surface or a persistent structural element of an app. In this post, with using
android.support.design.widget.BottomSheetBehavior
, I will make an output like this DEMO VIDEO:Import Design support library
app/build.gradle
before start coding:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.2.0'
compile 'com.android.support:design:23.2.0'
}
Designing layout in XML
BottomSheetBehavior
as a child view of a CoordinatorLayout
, adding app:layout_behavior="@string/bottom_sheet_behavior"
, you’ll automatically get the appropriate touch detection to transition between five state:- STATE_COLLAPSED: this collapsed state is the default and shows just a portion of the layout along the bottom. The height can be controlled with the
app:behavior_peekHeight
attribute (defaults to 0). - STATE_DRAGGING: the intermediate state while the user is directly dragging the bottom sheet up or down
- STATE_SETTLING: that brief time between when the View is released and settling into its final position.
- STATE_EXPANDED: the fully expanded state of the bottom sheet, where either the whole bottom sheet is visible (if its height is less than the containing
CoordinatorLayout
) or the entireCoordinatorLayout
is filled. - STATE_HIDDEN: disabled by default (and enabled with the
app:behavior_hideable
attribute), enabling this allows users to swipe down on the bottom sheet to completely hide the bottom sheet.
GridView
):
activity_main.xml
<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="com.example.bottomsheetbehavior.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<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>
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<GridView
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="3"
app:behavior_hideable="true"
android:background="#dddddd"
app:layout_behavior="@string/bottom_sheet_behavior" />
</android.support.design.widget.CoordinatorLayout>
Setting up in programmatically code
BottomSheetBehavior
. It has a really handy method from(view)
that is used to take the instance of the behavior from the layout params, of course if they are of same type. Moreover, the BottomSheetBehavior
can pin BottomSheetCallback
to receive callbacks like state changes and offset changes for your sheet. Setting up process done by this code:
bottomSheet = (GridView) findViewById(R.id.bottom_sheet);
bottomSheet.setTranslationY(getStatusBarHeight());
BottomSheetBehavior sheetBehavior = BottomSheetBehavior.from(bottomSheet);
sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
boolean first = true;
@Override
public void onStateChanged(View bottomSheet, int newState) {
Log.d("MainActivity", "onStateChanged: " + newState);
//TODO: add more code here
}
@Override
public void onSlide(View bottomSheet, float slideOffset) {
Log.d("MainActivity", "onSlide: ");
if (first) {
first = false;
bottomSheet.setTranslationY(0);
}
}
});
Full Code of the project
ListView
, after click at any row, the bottom sheet will be displayed. Create an adapter for it first:
BooksAdapter.java
package info.devexchanges.bottomsheetmd;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.List;
public class BooksAdapter extends ArrayAdapter<Book> {
private List<Book> books;
private Activity activity;
public BooksAdapter(Activity context, int resource, List<Book> objects) {
super(context, resource, objects);
this.activity = context;
this.books = objects;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.item_list_view, null, false);
} else {
convertView.getTag();
}
TextView name = (TextView) convertView.findViewById(R.id.book_name);
TextView author = (TextView) convertView.findViewById(R.id.book_author);
name.setText("Book name: " + getItem(position).getName());
author.setText("Author: " + getItem(position).getAuthor());
return convertView;
}
}
Layout for each ListView
item:
item_listview.xml
As note above, the bottom sheet is a <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/book_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_horizontal_margin"
android:textColor="@android:color/holo_green_dark"
android:textStyle="bold" />
<TextView
android:id="@+id/book_author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:textColor="@android:color/holo_blue_dark" />
</LinearLayout>
GridView
, some "action button" were placed here, this is it's adapter (based on ArrayAdapter
, too):
BottomSheetAdapter.java
And it's each item layout (only an package info.devexchanges.bottomsheetmd;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class BottomSheetAdapter extends ArrayAdapter<Integer> {
private Activity activity;
public BottomSheetAdapter(Activity context, int resource, Integer[] objects) {
super(context, resource, objects);
this.activity = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewHolder holder;
if (row == null) {
LayoutInflater inflater = activity.getLayoutInflater();
row = inflater.inflate(R.layout.item_grid, parent, false);
holder = new ViewHolder();
holder.image = (ImageView) row.findViewById(R.id.image);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
holder.image.setImageResource(getItem(position));
return row;
}
private static class ViewHolder {
ImageView image;
}
}
ImageView
):
item_grid.xml
The most important file is the main activity. Handling each <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:contentDescription="@string/app_name"
android:padding="10dp" />
</RelativeLayout>
ListView
and bottom sheet(GridView
) item click event here:
MainActivity.java
The POJO of the project:
package info.devexchanges.bottomsheetmd;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.design.widget.BottomSheetBehavior;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private ListView listView;
private GridView bottomSheet;
private ArrayAdapter<Integer> bottomSheetAdapter;
private Toolbar toolbar;
private ArrayAdapter<Book> adapter;
private ArrayList<Book> books;
private Book selectedBook;
private BottomSheetBehavior sheetBehavior;
private Integer[] bottomItems = {R.drawable.add, R.drawable.mail, R.drawable.delete, R.drawable.facebook,
R.drawable.google_plus, R.drawable.twitter};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createBooksData();
listView = (ListView) findViewById(R.id.list_view);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//set main ListView adapter
adapter = new BooksAdapter(this, R.layout.item_list_view, books);
listView.setAdapter(adapter);
//set bottom sheet(GridView) adapter
bottomSheetAdapter = new BottomSheetAdapter(this, R.layout.item_grid, bottomItems);
bottomSheet.setAdapter(bottomSheetAdapter);
bottomSheet = (GridView) findViewById(R.id.bottom_sheet);
bottomSheet.setTranslationY(getStatusBarHeight());
sheetBehavior = BottomSheetBehavior.from(bottomSheet);
sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
boolean first = true;
@Override
public void onStateChanged(View bottomSheet, int newState) {
Log.d("MainActivity", "onStateChanged: " + newState);
}
@Override
public void onSlide(View bottomSheet, float slideOffset) {
Log.d("MainActivity", "onSlide: ");
if (first) {
first = false;
bottomSheet.setTranslationY(0);
}
}
});
//main listview item click event
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectedBook = (Book) parent.getAdapter().getItem(position);
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
});
//bottom sheet item click event
bottomSheet.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
if (position == 2) {
//delete an item in listview
new AlertDialog.Builder(MainActivity.this)
.setTitle("Delete Confirm")
.setMessage("Are you sure you want to delete " + "\"" + selectedBook.getName().toUpperCase() + "\"" + "?")
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//delete it
books.remove(selectedBook);
adapter.notifyDataSetChanged();
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
})
.setNegativeButton(android.R.string.no, null)
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
} else if (position == 0) {
//add new book to main ListView
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(MainActivity.this);
alertBuilder.setTitle("Adding a book");
alertBuilder.setMessage("Input book name and author");
LinearLayout layout = new LinearLayout(MainActivity.this);
layout.setOrientation(LinearLayout.VERTICAL);
final EditText name = new EditText(MainActivity.this);
name.setHint("Book title");
layout.addView(name);
final EditText author = new EditText(MainActivity.this);
author.setHint("Book's author");
layout.addView(author);
alertBuilder.setView(layout);
alertBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//add this item
books.add(new Book(name.getText().toString(), author.getText().toString()));
adapter.notifyDataSetChanged();
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
});
alertBuilder.setNegativeButton(android.R.string.no, null);
alertBuilder.setIcon(android.R.drawable.ic_dialog_alert);
alertBuilder.show();
} else if (position == 3) {
Toast.makeText(getBaseContext(), "Share this on Facebook", Toast.LENGTH_SHORT).show();
} else if (position == 4) {
Toast.makeText(getBaseContext(), "Share this on Google+", Toast.LENGTH_SHORT).show();
} else if (position == 5) {
Toast.makeText(getBaseContext(), "Share this on Twitter", Toast.LENGTH_SHORT).show();
}
}
});
}
private void createBooksData() {
books = new ArrayList<>();
books.add(new Book("Lão Hạc", "Nam Cao"));
books.add(new Book("Số đỏ", "Vũ Trọng Phụng"));
books.add(new Book("Tắt đèn", "Ngô Tất Tố"));
books.add(new Book("The wild chase sheep", "Haruki Murakami"));
books.add(new Book("Mảnh trăng cuối rừng", "Nguyễn Minh Châu"));
books.add(new Book("The Adventures of Huckleberry Finn", "Mark Twain"));
books.add(new Book("Dế Mèn phiêu lưu ký", "Tô Hoài"));
books.add(new Book("Never let me go", "Kazuo Ishiguro"));
books.add(new Book("Harry Potter", "J.K. Rowling"));
books.add(new Book("The Last Leaf", "O. Henry"));
books.add(new Book("The Call of the Wild", "Jack London"));
books.add(new Book("Le Papa de Simon", "Guy de Maupassant"));
books.add(new Book("One Hundred Years of Solitude", "Gabriel García Márquez"));
}
private int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
@Override
public void onBackPressed() {
if (sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
super.onBackPressed();
}
}
}
Book.java
Make sure that you use "AppCompat no Action Bar theme" with this design:
package info.devexchanges.bottomsheetmd;
public class Book {
private String name;
private String author;
public Book(String name, String author) {
this.name = name;
this.author = author;
}
public String getName() {
return name;
}
public String getAuthor() {
return author;
}
}
styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Dark" />
</resources>
Running the application
Clicking at bottom sheet "Delete button":
Clicking at the bottom sheet "Add button":
Conclusions
Update: Modal bottom sheet
Please see my updated post to learn the way to create it! :)