81

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

John Shelley
  • 2,655
  • 3
  • 27
  • 36

9 Answers9

67

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).

Community
  • 1
  • 1
reVerse
  • 35,075
  • 22
  • 89
  • 84
  • Boom. This is exactly what I'm looking for! I'll give it a try tonight! – John Shelley Nov 03 '14 at 21:40
  • 1
    this is great, thanks for sharing! What if you want to display all options that would show up in a share intent? They would be dynamically generated based on the apps installed on the device. Any thoughts on how that can be accomplished? – Cat Feb 20 '15 at 17:20
  • 1
    @Cat Well basically you just need to get a List of all applications that can handle the intent (see [this StackO-Question](http://stackoverflow.com/questions/9083826/get-the-list-of-apps-capable-of-handling-the-send-intent-to-display-in-a-view-n) for example) and add them to the adapter which "feeds" the BottomSheet. You can also see the official sample of the lib which does exactly what you're requesting. ([Click - Line 153 - 179](https://github.com/soarcn/BottomSheet/blob/master/example/src/main/java/com/cocosw/bottomsheet/example/ListAcitivty.java)). – reVerse Feb 20 '15 at 22:49
  • that worked, thanks @reVerse! I ended up reusing the code in the `getShareActions` method to get all possible sharing options. Btw, also useful to call `.limit(R.integer.num_share_actions)` on the builder unless you want the sheet to cover the entire screen – Cat Mar 01 '15 at 01:35
  • This library is so awesome ! – kuldeep Sep 19 '15 at 21:01
  • @reVerse I want to implement Bottom sheet with a fab icon as given in android material specs. (http://bit.ly/1O8EQrK). Is it possible with this library? – Koustuv Sinha Nov 14 '15 at 15:23
  • @KoustuvSinha I don't think so. The screenshot you've posted looks more like a Fragment with a custom view that gets translated onto the screen than a menu which this library basically tries to replicate. – reVerse Nov 14 '15 at 15:29
  • @KoustuvSinha Have a look at the [AndroidSlidingUpPanel](https://github.com/umano/AndroidSlidingUpPanel) library. – reVerse Nov 14 '15 at 17:36
  • @reVerse I ended up accepting the Google Support Library as the correct answer just so we don't confuse any new visitors to this question. The answer you posted is definitely still valid though! I hope thats okay. – John Shelley Feb 25 '16 at 18:39
  • 1
    @JohnShelley That's all right, absolutely. I'd also recommend going for the "official solution" though it's really limited at the moment but at least Google laid the foundation, finally. ;) – reVerse Feb 25 '16 at 22:59
  • it's ALMOST perfect. One thing is when selecting an item in menu, the background of that item turns into NASTY Yellow/Orange color. Any idea how to change it??? – ekashking Jul 23 '20 at 17:09
66

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.

Community
  • 1
  • 1
John Shelley
  • 2,655
  • 3
  • 27
  • 36
  • Thanks for posting. I think this should be the "Correct" answer now :) – vine'th Feb 25 '16 at 08:53
  • @androiddeveloper done. considering its a support specific thing now, I feel users will have better luck looking for those examples. When I asked the question originally it wasn't provided in the support lib. If the question was "How to implement Support Library bottom sheet view" then I would've provided a more in depth answer too :) – John Shelley Mar 13 '16 at 00:34
  • @JohnShelley Don't you need to use some XML stuff too? Is it possible to set the bottom sheet peek/collapsed (or whatever it's called about when it's at the bottom) to have a wrap-content view , and when it's expanded, to be full screen? – android developer Mar 13 '16 at 10:21
  • 4
    I think there are many people wondering how to use Google new support API, and Vipul Shah has a great YouTube link that can help you understand how to use it, please refer to the YouTube link in http://stackoverflow.com/a/35731959/1053037 – minh truong Mar 25 '16 at 02:11
  • the question remains: how to implement the bottom sheet menu. Where are menu items in the example above??????? You know, the menu items that you click ... – ekashking Jul 23 '20 at 21:32
8

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.

Tyler
  • 19,113
  • 19
  • 94
  • 151
mcorrado
  • 91
  • 1
  • 3
7

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");
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Vipul
  • 27,808
  • 7
  • 60
  • 75
5

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/

Zheko
  • 673
  • 6
  • 16
5

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.
vine'th
  • 4,890
  • 2
  • 27
  • 27
3

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!

Fifer Sheep
  • 2,900
  • 2
  • 30
  • 47
Siru malayil
  • 197
  • 2
  • 11
0

Google recently released Android Support Library 23.2 which officially brings Bottom sheets to the Android Design Support Library.

Mehlyfication
  • 498
  • 5
  • 9
-1

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
     }
    
Android Developer
  • 9,157
  • 18
  • 82
  • 139