-
Notifications
You must be signed in to change notification settings - Fork 0
Home
jonikay edited this page Jul 5, 2026
·
3 revisions
Copy each example into your project and adapt as needed.
import com.bottomshelfer.BottomShelferDetent
import com.bottomshelfer.BottomShelferDialog
import com.bottomshelfer.BottomShelferSheet
val sheet = BottomShelferSheet(context)
sheet.setDetents(listOf(BottomShelferDetent.medium(context)))
val content = TextView(context).apply {
text = "Hello, BottomShelfer!"
setPadding(24, 60, 24, 24)
setTextColor(Color.BLACK)
}
sheet.addContentView(content)
val dialog = BottomShelferDialog(context, sheet)
dialog.dismissOnHide = true
dialog.show()val sheet = BottomShelferSheet(context)
sheet.setDetents(listOf(
BottomShelferDetent.small(context),
BottomShelferDetent.medium(context),
BottomShelferDetent.large(context)
))
sheet.setSelectedDetentIndex(1)
val content = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(24, 60, 24, 24)
addView(Button(context).apply {
text = "Snap to small (25%)"
setOnClickListener {
sheet.snapToHeight(BottomShelferDetent.small(context).height)
}
})
addView(Button(context).apply {
text = "Snap to large (90%)"
setOnClickListener {
sheet.snapToHeight(BottomShelferDetent.large(context).height)
}
})
addView(Button(context).apply {
text = "Dismiss"
setOnClickListener { sheet.parentDialog?.dismiss() }
})
}
sheet.addContentView(content)
val dialog = BottomShelferDialog(context, sheet)
dialog.show()val sheet = BottomShelferSheet(context)
sheet.config = BottomShelferLayoutConfig(
maxSheetWidthDp = 500,
grabberPillWidthDp = 60,
grabberPillHeightDp = 8,
grabberPillBottomOffsetDp = 14,
maxHeightFraction = 0.6f,
cornerRadiusDp = 28f,
dimmingColor = 0x66000000.toInt(),
)
sheet.setDetents(BottomShelferDetent.detentsForContentHeight(1000, context, 0.6f))
sheet.setSelectedDetentIndex(2)
val content = TextView(context).apply {
text = "Custom layout: wider, bigger grabber.\n60% max height, 28pt corners."
setPadding(24, 60, 24, 24)
setTextColor(Color.BLACK)
}
sheet.addContentView(content)
val dialog = BottomShelferDialog(context, sheet)
dialog.dismissOnHide = true
dialog.show()val sheet = BottomShelferSheet(context)
sheet.config = sheet.config.copy(isDimmingEnabled = false)
sheet.setDetents(listOf(BottomShelferDetent.medium(context)))
val content = TextView(context).apply {
text = "No dimming scrim behind this sheet."
setPadding(24, 60, 24, 24)
setTextColor(Color.BLACK)
}
sheet.addContentView(content)
val dialog = BottomShelferDialog(context, sheet)
dialog.dismissOnHide = true
dialog.show()val sheet = BottomShelferSheet(context)
sheet.config = sheet.config.copy(isDraggingEnabled = false)
sheet.setDetents(listOf(BottomShelferDetent.small(context)))
sheet.setSelectedDetentIndex(0)
val content = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(24, 60, 24, 24)
addView(TextView(context).apply {
text = "This sheet cannot be dragged."
setTextColor(Color.BLACK)
})
addView(Button(context).apply {
text = "Dismiss"
setOnClickListener { sheet.parentDialog?.dismiss() }
})
}
sheet.addContentView(content)
val dialog = BottomShelferDialog(context, sheet)
dialog.show()val sheet = BottomShelferSheet(context)
sheet.config = BottomShelferLayoutConfig(
grabberPillWidthDp = 0,
grabberPillHeightDp = 0,
grabberPillBottomOffsetDp = 0,
)
sheet.setDetents(listOf(
BottomShelferDetent.small(context),
BottomShelferDetent.medium(context)
))
sheet.setSelectedDetentIndex(1)
val content = TextView(context).apply {
text = "Hidden grabber pill.\nDrag still works via the top area!"
setPadding(24, 60, 24, 24)
setTextColor(Color.BLACK)
}
sheet.addContentView(content)
val dialog = BottomShelferDialog(context, sheet)
dialog.dismissOnHide = true
dialog.show()val sheet = BottomShelferSheet(context)
sheet.autoFocus = true
sheet.setDetents(listOf(BottomShelferDetent.custom(500)))
sheet.setSelectedDetentIndex(0)
val content = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(24, 60, 24, 24)
addView(TextView(context).apply {
text = "Tap the field — the sheet lifts for the keyboard."
setTextColor(Color.BLACK)
})
addView(EditText(context).apply {
hint = "Type something..."
inputType = InputType.TYPE_CLASS_TEXT
})
addView(Button(context).apply {
text = "Dismiss"
setOnClickListener { sheet.parentDialog?.dismiss() }
})
}
sheet.addContentView(content)
val dialog = BottomShelferDialog(context, sheet)
dialog.dismissOnHide = true
dialog.show()Set
sheet.autoFocus = trueto automatically focus the firstEditTextand show the keyboard when the sheet appears.
sheet.callback = object : BottomShelferCallback {
override fun onDismiss() {
Log.d("BottomShelfer", "Sheet dismissed")
}
override fun onGrabberDragBegan() {
Log.d("BottomShelfer", "Grabber drag started")
}
override fun onGrabberDragEnded() {
Log.d("BottomShelfer", "Grabber drag ended")
}
override fun onContentDragBegan() {
Log.d("BottomShelfer", "Content drag started")
}
override fun onContentDragEnded() {
Log.d("BottomShelfer", "Content drag ended")
}
override fun onDetentChanged(index: Int, height: Int) {
Log.d("BottomShelfer", "Snapped to detent $index at ${height}pt")
}
}val sheet = BottomShelferSheet(context)
sheet.setDetents(listOf(
BottomShelferDetent.small(context),
BottomShelferDetent.medium(context),
BottomShelferDetent.large(context)
))
sheet.setSelectedDetentIndex(1)
val recyclerView = RecyclerView(context).apply {
layoutManager = LinearLayoutManager(context)
adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
object : RecyclerView.ViewHolder(
TextView(parent.context).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
setPadding(48, 24, 48, 24)
setTextColor(Color.BLACK)
}
) {}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = "Row ${position + 1}"
}
override fun getItemCount() = 50
}
}
sheet.addContentView(recyclerView,
ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))
val dialog = BottomShelferDialog(context, sheet)
dialog.show()Nested scrolling with
RecyclerView,ScrollView, andNestedScrollViewis handled automatically.
val sheet = BottomShelferSheet(context)
sheet.setDetents(listOf(
BottomShelferDetent.small(context),
BottomShelferDetent.medium(context),
BottomShelferDetent.large(context)
))
sheet.setSelectedDetentIndex(1)
val content = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(24, 60, 24, 24)
val composeView = ComposeView(context).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setViewTreeLifecycleOwner(activity)
setViewTreeViewModelStoreOwner(activity)
setViewTreeSavedStateRegistryOwner(activity)
setContent {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
var name by remember { mutableStateOf("") }
TextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
modifier = Modifier.fillMaxWidth()
)
var enabled by remember { mutableStateOf(true) }
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text("Enabled")
Spacer(Modifier.weight(1f))
Switch(checked = enabled, onCheckedChange = { enabled = it })
}
}
}
}
addView(composeView, LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
}
sheet.addContentView(content)
val dialog = BottomShelferDialog(context, sheet)
dialog.show()Hide the built-in pill and add your own view at the top of the sheet, using callbacks to animate it during drag.
val sheet = BottomShelferSheet(context)
sheet.config = sheet.config.copy(
grabberPillWidthDp = 0,
grabberPillHeightDp = 0,
grabberPillBottomOffsetDp = 0,
)
sheet.setDetents(listOf(
BottomShelferDetent.small(context),
BottomShelferDetent.medium(context)
))
sheet.setSelectedDetentIndex(1)
class RainbowGrabberView(context: Context) : View(context) {
private val colors = intArrayOf(
Color.RED, Color.rgb(255, 165, 0), Color.YELLOW,
Color.GREEN, Color.BLUE, Color.rgb(128, 0, 128)
)
private var gradient: LinearGradient? = null
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
gradient = LinearGradient(0f, 0f, w.toFloat(), 0f, colors, null, Shader.TileMode.CLAMP)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.shader = gradient
canvas.drawRoundRect(0f, 0f, width.toFloat(), height.toFloat(), 6f, 6f, paint)
}
}
val rainbowGrabber = RainbowGrabberView(context)
sheet.callback = object : BottomShelferCallback {
override fun onGrabberDragBegan() {
rainbowGrabber.animate()
.scaleX(1.4f).scaleY(1.8f)
.alpha(0.5f)
.setDuration(200)
.start()
}
override fun onGrabberDragEnded() {
rainbowGrabber.animate()
.scaleX(1.0f).scaleY(1.0f)
.alpha(1.0f)
.setDuration(200)
.start()
}
}
val container = FrameLayout(context)
container.addView(rainbowGrabber, FrameLayout.LayoutParams(96, 12).apply {
gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
topMargin = 8
})
sheet.addContentView(container)
val dialog = BottomShelferDialog(context, sheet)
dialog.dismissOnHide = true
dialog.show()val sheet = BottomShelferSheet(context)
sheet.config = BottomShelferLayoutConfig(
maxSheetWidthDp = 430, // clamp width on tablets
maxHeightFraction = 0.9f, // % of screen height
grabberHitAreaHeightDp = 44, // draggable band height
grabberPillWidthDp = 60, // pill width
grabberPillHeightDp = 8, // pill height
grabberPillColor = 0x99000000.toInt(), // pill color (ARGB)
grabberPillBottomOffsetDp = 12, // distance from sheet top edge
grabberPillCornerRadiusDp = 2.5f, // pill corner rounding
cornerRadiusDp = 28f, // sheet corner rounding
isDimmingEnabled = true, // show dimming scrim behind sheet
isDraggingEnabled = true, // allow drag gestures
dimmingColor = 0x4D000000.toInt(), // scrim color (ARGB)
)Set dismissOnHide on the dialog:
-
true— tapping the dimming scrim immediately dismisses the sheet. -
false(default) — first tap on the scrim dismisses the keyboard (if visible); second tap dismisses the sheet.
val dialog = BottomShelferDialog(context, sheet)
dialog.dismissOnHide = true // immediate dismiss on scrim tap
dialog.show()