A customizable slide-up bottom sheet for Android. Supports arbitrary height detents, a draggable grabber, optional dimming scrim, keyboard avoidance, rotation, scroll view coordination, and full layout configuration.
- Android 5.0+ (API 21+)
- Kotlin 1.9+
Add JitPack to your root settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}Then add the dependency:
// app/build.gradle.kts
dependencies {
implementation("com.github.jonikay89:BottomShelfer-android:1.0.0")
}Clone the repo and reference the module directly:
// settings.gradle.kts
include(":bottomshelfer")
project(":bottomshelfer").projectDir = file("../BottomShelfer-android/bottomshelfer")// app/build.gradle.kts
dependencies {
implementation(project(":bottomshelfer"))
}val sheet = BottomShelferSheet(context)
sheet.setDetents(listOf(
BottomShelferDetent.medium(context),
BottomShelferDetent.large(context)
))
sheet.setSelectedDetentIndex(0)
sheet.addContentView(myContentView)
val dialog = BottomShelferDialog(context, sheet)
dialog.show()Predefined or custom-height snap points:
sheet.setDetents(listOf(BottomShelferDetent.medium(context), BottomShelferDetent.large(context)))
sheet.setDetents(listOf(BottomShelferDetent.custom(320)))
sheet.setDetents(BottomShelferDetent.detentsForContentHeight(420, context))
sheet.setDetents(BottomShelferDetent.detentsForContentHeight(420, context, maxHeightFraction = 0.6f))
sheet.setSelectedDetentIndex(1)Factory methods on BottomShelferDetent:
small(context)— 25% of screen heightmedium(context)— 50% of screen heightlarge(context)— 90% of screen heightcustom(height)— explicit pixel heightdetentsForContentHeight(height, context, maxHeightFraction = 0.9f)— auto-generates three detents scaled relative to content height:- small at 40%, medium at 100%, large at 150% of content height
Size, offset, and corner radius are configurable via BottomShelferLayoutConfig:
sheet.config = BottomShelferLayoutConfig(
grabberPillWidthDp = 56,
grabberPillHeightDp = 6,
grabberPillCornerRadiusDp = 3f,
)The pill animates on drag — scales to 1.3x, fades to 0.6 alpha. Set its size to
0 to hide while keeping the drag gesture active.
Optional semi-transparent backdrop:
sheet.config = sheet.config.copy(isDimmingEnabled = false)
sheet.config = sheet.config.copy(dimmingColor = 0x66000000.toInt())Dismiss on scrim tap is controlled via the dialog:
val dialog = BottomShelferDialog(context, sheet)
dialog.dismissOnHide = true // dismiss when scrim tapped
dialog.show()The sheet coordinates with embedded scroll views (RecyclerView, ScrollView, NestedScrollView). When the scroll view is pinned to the top, a downward drag transfers control to the sheet.
Disable dragging entirely with isDraggingEnabled = false.
The sheet's dialog window uses SOFT_INPUT_ADJUST_RESIZE so it naturally
adjusts when the keyboard appears.
Set autoFocus = true to automatically focus the first EditText and show the
keyboard when the sheet opens:
sheet.autoFocus = truesheet.callback = object : BottomShelferCallback {
override fun onDismiss() { /* sheet dismissed */ }
override fun onGrabberDragBegan() { /* grabber drag started */ }
override fun onGrabberDragEnded() { /* grabber drag ended */ }
override fun onContentDragBegan() { /* content drag started */ }
override fun onContentDragEnded() { /* content drag ended */ }
override fun onDetentChanged(index: Int, height: Int) {
/* snapped to detent index at height */
}
}sheet.snapToHeight(320)The sheet re-derives its detents when the configuration changes (rotation, multi-window resize).
Override defaults through BottomShelferLayoutConfig:
| Property | Default | Description |
|---|---|---|
maxSheetWidthDp |
430 | Clamps sheet width on tablets |
maxHeightFraction |
0.9 | Caps sheet height as fraction of container |
grabberHitAreaHeightDp |
44 | Height of the draggable band |
grabberPillWidthDp / grabberPillHeightDp |
60 / 8 | Pill dimensions |
grabberPillColor |
0x99000000 |
Pill color (with alpha) |
grabberPillBottomOffsetDp |
12 | Distance from sheet edge to pill |
grabberPillCornerRadiusDp |
2.5 | Pill corner radius |
cornerRadiusDp |
28 | Sheet top corner radius |
isDimmingEnabled |
true |
Whether the scrim backdrop is shown |
isDraggingEnabled |
true |
Whether the sheet can be dragged |
dimmingColor |
0x4D000000 |
Scrim color (with alpha) |
sheet.config = BottomShelferLayoutConfig(
maxSheetWidthDp = 500,
maxHeightFraction = 0.6f,
cornerRadiusDp = 28f,
)Embed Jetpack Compose content inside a bottom sheet via ComposeView:
val composeView = ComposeView(context).apply {
setContent { MyComposeContent() }
}
sheet.addContentView(composeView)MIT — see LICENSE.
jonikay89 — @jonikay89
