Android How to implement Bottom Sheet from Material Design docs

AndroidAndroid 5.0-LollipopMaterial DesignFloating Action-Button

Android 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 ->

enter image description here

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:

picture of the bottom sheet

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

Bottom Sheet image

If you want to achieve bottom sheet like this, follow this design pattern few simple steps

  1. create bottom_sheet_layout.xml layout file

  2. 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:-

enter image description here

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

  1. 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
     }
    

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJohn ShelleyView Question on Stackoverflow
Solution 1 - AndroidreVerseView Answer on Stackoverflow
Solution 2 - AndroidJohn ShelleyView Answer on Stackoverflow
Solution 3 - AndroidmcorradoView Answer on Stackoverflow
Solution 4 - AndroidVipulView Answer on Stackoverflow
Solution 5 - AndroidZhekoView Answer on Stackoverflow
Solution 6 - Androidvine'thView Answer on Stackoverflow
Solution 7 - AndroidSiru malayilView Answer on Stackoverflow
Solution 8 - AndroidMehlyficationView Answer on Stackoverflow
Solution 9 - AndroidAndroid DeveloperView Answer on Stackoverflow