Android How to implement Bottom Sheet from Material Design docs
AndroidAndroid 5.0-LollipopMaterial DesignFloating Action-ButtonAndroid Problem Overview
How do you implement the bottom sheet specficiation? http://www.google.com/design/spec/components/bottom-sheets.html
The new update to Google Drive shows this with the Floating Action Button press ->
Granted the specs never say anything about rounded corners, regardless it is possible to do, just unsure of how to go about it. Currently using the AppCompat library and target set to 21.
Thanks
Android Solutions
Solution 1 - Android
Edit
The BottomSheet
is now part of the android-support-library
. See John Shelleys' answer.
Unfortunately there's currently no "official" way on how to do this (at least none that I'm aware of).
Luckily there's a library called "BottomSheet" (click) which mimics the look and feel of the BottomSheet
and supports Android 2.1 and up.
In case of the Drive app, here's how the code would look like with this library:
new BottomSheet.Builder(this, R.style.BottomSheet_Dialog)
.title("New")
.grid() // <-- important part
.sheet(R.menu.menu_bottom_sheet)
.listener(new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO
}
}).show();
menu_bottom_sheet (basically a standard /res/menu/*.xml resource)
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/folder"
android:title="Folder"
android:icon="@drawable/ic_action_folder" />
<item
android:id="@+id/upload"
android:title="Upload"
android:icon="@drawable/ic_action_file_upload" />
<item
android:id="@+id/scan"
android:title="Scan"
android:icon="@drawable/ic_action_camera_alt" />
</menu>
Output looks like this:
Which, I think, comes pretty close to the original. If you're not happy with the colors you can customize it - see this (click).
Solution 2 - Android
Answering my own question so developers know that the new support library provides this finally! All hail the all powerful Google!
An example from the Android Developer's Blog:
> // The View with the BottomSheetBehavior
> View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);
> BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
> behavior.setBottomSheetCallback(new 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
> }
> });
@reVerse's answer above is still a valid option but its nice to know that there is a standard that Google supports too.
Solution 3 - Android
Following the blog post: <http://android-developers.blogspot.com/2016/02/android-support-library-232.html>
My xml ended up looking like this:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/coordinator_layout"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
<ImageView
android:src="@android:drawable/ic_input_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
And in my onCreateView of my fragment:
coordinatorLayout = (CoordinatorLayout)v.findViewById(R.id.coordinator_layout);
View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setPeekHeight(100);
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
}
});
The default of setPeekHeight is 0, so if you don't set it, you won't be able to see your view.
Solution 4 - Android
You can now use Official BottomSheetBehavior
API from android support library 23.2.
Below is sample code snippet
bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.bottomSheet));
case R.id.expandBottomSheetButton:
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
break;
case R.id.collapseBottomSheetButton:
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
break;
case R.id.hideBottomSheetButton:
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
break;
case R.id.showBottomSheetDialogButton:
new MyBottomSheetDialogFragment().show(getSupportFragmentManager(), "sample");
Please refer to Android BottomSheet youtube tutorial to get understanding on it.
Solution 5 - Android
I would go with a straight corners as it is in the guidelines. As for the implementation - maybe it is best to use the idea from this project: https://github.com/umano/AndroidSlidingUpPanel
I think that you could use it as it is or take the idea for the implementation. Another great article on how to implement similar sliding panel can be found here: http://blog.neteril.org/blog/2013/10/10/framelayout-your-best-ui-friend/
Solution 6 - Android
Here are some of the other options :
- There is one available from Flipboard, however the embedding activity needs to be modified for the bottomsheet to work.
- tutti-ch's bottomsheet : This has been extracted from Android Repo's ResolverActivity and the launching activity need not be modified.
Solution 7 - Android
If you want to achieve bottom sheet like this, follow this design pattern few simple steps
-
create
bottom_sheet_layout.xml
layout file -
create
bottom_sheet_background.xml
drawable file
set your bottom_sheet_background.xml
drawable file like this
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/bottom_sheet_background"/>
<corners
android:topRightRadius="20dp"
android:topLeftRadius="20dp"/>
</shape>
your bottom_sheet_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/bottom_Sheet"
android:background="@drawable/bottom_sheet_background"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:paddingTop="16dp"
android:paddingBottom="42dp"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/rectangle_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/rectangle_39"
/>
//add your design code here
</LinearLayout>
And your activity_main.xml
or fragment
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
//design your code here
//this is your bottom sheet layout included here
<include
android:id="@+id/bottom_sheet_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.
bottomsheet.BottomSheetBehavior"
layout="@layout/bottom_sheet_layout"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Finally, add code in your MainActivity
or Fragment
class. Here I am adding Kotlin code inside your onCreate
or onCreateView
BottomSheetBehavior.from(binding.bottomSheetLayout.bottomSheet).apply {
//peek height is default visible height
peekHeight = 200
this.state = BottomSheetBehavior.STATE_COLLAPSED
}
That's it!
Solution 8 - Android
Google recently released Android Support Library 23.2 which officially brings Bottom sheets to the Android Design Support Library.
Solution 9 - Android
Now with Android Jetpack Compose released which is Android's modern UI toolkit , Bottomsheets can be made more easily without using any xml code:-
1.To create Persistent bottom sheet where user can access the content outside of the bottom sheet’s scope:-
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
)
val coroutineScope = rememberCoroutineScope()
MaterialTheme {
Column {
BottomSheetScaffold(
modifier = Modifier.fillMaxSize(),
topBar = { TopAppBar(viewModel, onNavigateToRecipeListScreen, hideKeyBoard) },
content = {
CreateRecipeContent(
viewModel,
context,
readExternalStorage,
bottomSheetScaffoldState,
coroutineScope
)
},
scaffoldState = bottomSheetScaffoldState,
sheetContent = {
Column(
Modifier
.fillMaxWidth()
.height(200.dp)
.background(color = colorResource(id = R.color.colorPrimaryLight))
)
{
Text(
text = "SELECT PICTURE",
style = TextStyle(fontSize = 26.sp),
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp)
.align(Alignment.Start),
color = Color.Black
)
Spacer(modifier = Modifier.height(16.dp))
IconButton(onClick = {
when {
context.let { it1 ->
ContextCompat.checkSelfPermission(
it1,
Manifest.permission.READ_EXTERNAL_STORAGE
)
} == PackageManager.PERMISSION_GRANTED -> {
val takePictureIntent =
Intent(MediaStore.ACTION_IMAGE_CAPTURE)
launchCamera(takePictureIntent)
coroutineScope.launch {
bottomSheetScaffoldState.bottomSheetState.collapse()
}
}
else -> {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
viewModel.isCameraPermissionAsked = true
readExternalStorage()
coroutineScope.launch {
bottomSheetScaffoldState.bottomSheetState.collapse()
}
}
}
}, modifier = Modifier.fillMaxWidth()) {
Text(
text = "TAKE PHOTO",
style = TextStyle(fontSize = 20.sp),
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp)
.align(Alignment.Start),
textAlign = TextAlign.Left,
color = Color.Black
)
}
Spacer(modifier = Modifier.height(16.dp))
IconButton(onClick = {
when {
context.let { it1 ->
ContextCompat.checkSelfPermission(
it1,
Manifest.permission.READ_EXTERNAL_STORAGE
)
} == PackageManager.PERMISSION_GRANTED -> {
val galleryIntent = Intent(
Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
)
galleryIntent.type = "image/*"
launchGalley(galleryIntent)
coroutineScope.launch {
bottomSheetScaffoldState.bottomSheetState.collapse()
}
}
else -> {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
viewModel.isCameraPermissionAsked = false
readExternalStorage()
coroutineScope.launch {
bottomSheetScaffoldState.bottomSheetState.collapse()
}
}
}
}, modifier = Modifier.fillMaxWidth()) {
Text(
text = "CHOOSE FROM GALLERY",
style = TextStyle(fontSize = 20.sp),
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(8.dp)
.align(Alignment.Start),
textAlign = TextAlign.Left,
color = Color.Black
)
}
}
}, sheetPeekHeight = 0.dp
)
}
}
Above code snipped and screenshot is from the app:-
https://play.google.com/store/apps/details?id=com.bhuvnesh.diary
created entirely using Jetpack Compose by me
-
To create Modal Bottom Sheet where users cannot access the content out of the bottom sheet’s scope:-
ModalBottomSheetLayout( sheetState = modalBottomSheetState, sheetElevation = 8.dp, sheetContent = { //sheet content } ) { ... //main content }