BottomSheetBehavior 是 Android Support Library(现 AndroidX)中 com.google.android.material.bottomsheet.BottomSheetBehavior 提供的一个行为类,用于实现底部弹出式面板(底部抽屉)效果,支持拖拽、展开/收起、状态监听等核心能力,广泛应用于底部菜单、筛选面板、详情弹窗等场景。
一、基础准备
1. 依赖引入
确保项目中引入 Material Design 依赖(AndroidX 版本):
gradle
dependencies {
// 核心 Material Design 库(包含 BottomSheetBehavior)
implementation 'com.google.android.material:material:1.12.0'
// 基础 AppCompat 库(可选,根据项目需求)
implementation 'androidx.appcompat:appcompat:1.7.0'
}
2. 布局基础结构
BottomSheetBehavior 需作用于 CoordinatorLayout 的子 View,核心布局结构如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 必须使用 CoordinatorLayout 作为父布局 -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 主内容区域(如 RecyclerView、LinearLayout 等) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/btnShowBottomSheet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="显示底部面板"/>
</LinearLayout>
<!-- 底部面板(BottomSheet) -->
<LinearLayout
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/white"
android:padding="16dp"
<!-- 核心属性:绑定 BottomSheetBehavior -->
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="底部面板标题"
android:textSize="18sp"
android:textStyle="bold"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="底部面板内容区域,支持拖拽展开/收起"
android:marginTop="8dp"/>
<Button
android:id="@+id/btnClose"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关闭面板"
android:marginTop="16dp"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
二、核心 API 与基础使用
1. 获取 BottomSheetBehavior 实例
在 Activity/Fragment 中获取 Behavior 实例,用于控制底部面板:
kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
// 声明 BottomSheetBehavior 实例
private lateinit var bottomSheetBehavior: BottomSheetBehavior<*>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化 BottomSheetBehavior
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
// 初始化点击事件
initClickEvents()
// 初始化 Behavior 配置
initBottomSheetConfig()
}
private fun initClickEvents() {
// 显示底部面板
btnShowBottomSheet.setOnClickListener {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
// 关闭底部面板
btnClose.setOnClickListener {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
}
}
2. 核心状态常量
BottomSheetBehavior 提供了多种状态控制面板行为,常用状态如下:
| 状态常量 | 说明 |
|---|---|
STATE_COLLAPSED |
折叠状态(默认高度,可通过 peekHeight 设置) |
STATE_EXPANDED |
完全展开状态(占满屏幕高度) |
STATE_HIDDEN |
隐藏状态(需先设置 behavior.setHideable(true)) |
STATE_HALF_EXPANDED |
半展开状态(Android 11+ 支持,需设置 fitToContents = false) |
STATE_DRAGGING |
拖拽中状态(不可手动设置,仅监听) |
STATE_SETTLING |
滑动中状态(不可手动设置,仅监听) |
3. 关键配置项
(1)设置折叠高度(peekHeight)
控制面板折叠时显示的高度,默认值为 56dp:
kotlin
private fun initBottomSheetConfig() {
// 设置折叠高度(单位:px)
bottomSheetBehavior.peekHeight = 200 // 也可使用 dp2px 工具类转换
// 允许隐藏面板(设置后才能使用 STATE_HIDDEN 状态)
bottomSheetBehavior.isHideable = true
// 是否适配内容高度(false 时支持半展开状态)
bottomSheetBehavior.isFitToContents = false
// 设置最大展开高度(Android 11+ 支持)
bottomSheetBehavior.maxWidth = resources.displayMetrics.widthPixels
bottomSheetBehavior.maxHeight = resources.displayMetrics.heightPixels / 2
}
// 工具类:dp 转 px
private fun dp2px(dp: Int): Int {
val density = resources.displayMetrics.density
return (dp * density + 0.5f).toInt()
}
(2)设置拖拽禁用
禁止用户手动拖拽面板,仅通过代码控制状态:
kotlin
bottomSheetBehavior.isDraggable = false
三、状态监听
通过 BottomSheetCallback 监听面板状态变化,实现业务逻辑联动:
kotlin
private fun initBottomSheetListener() {
bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
// 面板滑动时回调(dy:垂直滑动距离)
override fun onSlide(bottomSheet: View, slideOffset: Float) {
// slideOffset:滑动偏移量(0:折叠状态,1:完全展开,-1:隐藏)
Log.d("BottomSheet", "滑动偏移量:$slideOffset")
// 示例:根据偏移量改变面板透明度
bottomSheet.alpha = 0.8f + slideOffset * 0.2f
}
// 面板状态变化时回调
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> {
Log.d("BottomSheet", "完全展开")
// 展开时的业务逻辑:如隐藏软键盘、更新UI等
}
BottomSheetBehavior.STATE_COLLAPSED -> {
Log.d("BottomSheet", "折叠")
}
BottomSheetBehavior.STATE_HIDDEN -> {
Log.d("BottomSheet", "隐藏")
}
BottomSheetBehavior.STATE_HALF_EXPANDED -> {
Log.d("BottomSheet", "半展开")
}
BottomSheetBehavior.STATE_DRAGGING -> {
Log.d("BottomSheet", "拖拽中")
}
BottomSheetBehavior.STATE_SETTLING -> {
Log.d("BottomSheet", "滑动中")
}
}
}
})
}
四、高级用法
1. 全屏底部面板(无折叠高度)
实现点击按钮弹出全屏底部面板,且禁止折叠:
kotlin
// 初始化配置
bottomSheetBehavior.peekHeight = 0 // 折叠高度设为0
bottomSheetBehavior.isHideable = true
bottomSheetBehavior.isDraggable = true
// 显示全屏面板
btnShowFullScreenSheet.setOnClickListener {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
2. 嵌套滚动处理
若底部面板包含 RecyclerView/NestedScrollView,需处理嵌套滚动冲突:
xml
<!-- 底部面板内的 RecyclerView 配置 -->
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
同时在代码中启用嵌套滚动:
kotlin
recyclerView.isNestedScrollingEnabled = true
3. 自定义底部面板样式
通过设置背景、圆角、阴影美化面板:
xml
<!-- 底部面板根布局 -->
<LinearLayout
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_bottom_sheet"
android:elevation="8dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<!-- 内容 -->
</LinearLayout>
bg_bottom_sheet.xml(drawable 资源):
xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white"/>
<corners
android:topLeftRadius="16dp"
android:topRightRadius="16dp"/>
<padding android:all="16dp"/>
</shape>
五、常见问题与解决方案
1. 面板无法隐藏
- 原因:未设置
isHideable = true; - 解决方案:调用
bottomSheetBehavior.isHideable = true后再设置STATE_HIDDEN状态。
2. 半展开状态不生效
-
原因:Android 11 以下不支持,或未设置
isFitToContents = false; -
解决方案:
kotlinif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { bottomSheetBehavior.isFitToContents = false bottomSheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED }
3. 拖拽时与其他 View 冲突
- 原因:嵌套滚动未处理,或多个 Behavior 冲突;
- 解决方案:
- 禁用非目标 View 的拖拽:
bottomSheetBehavior.isDraggable = false; - 为嵌套滚动 View 设置
app:layout_behavior="@string/appbar_scrolling_view_behavior"; - 重写
onInterceptTouchEvent处理触摸事件。
- 禁用非目标 View 的拖拽:
4. 面板高度超出屏幕
- 原因:面板内容高度过高,未设置
wrap_content; - 解决方案:
- 面板根布局高度设为
wrap_content; - 内部使用滚动布局(如
NestedScrollView)包裹内容。
- 面板根布局高度设为
六、完整示例代码
布局文件(activity_main.xml)
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<Button
android:id="@+id/btnExpand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="展开面板"/>
<Button
android:id="@+id/btnCollapse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="折叠面板"
android:marginTop="8dp"/>
<Button
android:id="@+id/btnHide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="隐藏面板"
android:marginTop="8dp"/>
</LinearLayout>
<!-- 底部面板 -->
<LinearLayout
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_bottom_sheet"
android:elevation="8dp"
android:orientation="vertical"
android:padding="16dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="底部面板"
android:textSize="20sp"
android:textStyle="bold"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="200dp"
android:marginTop="16dp"/>
<Button
android:id="@+id/btnClose"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关闭"
android:marginTop="16dp"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Kotlin 代码(MainActivity.kt)
kotlin
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private lateinit var bottomSheetBehavior: BottomSheetBehavior<*>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化 BottomSheetBehavior
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
// 初始化配置
initConfig()
// 初始化监听
initListener()
// 初始化点击事件
initClick()
// 初始化 RecyclerView(示例)
initRecyclerView()
}
private fun initConfig() {
// 设置折叠高度
bottomSheetBehavior.peekHeight = dp2px(100)
// 允许隐藏
bottomSheetBehavior.isHideable = true
// 支持半展开(Android 11+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
bottomSheetBehavior.isFitToContents = false
}
// 禁用拖拽(可选)
// bottomSheetBehavior.isDraggable = false
}
private fun initListener() {
bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
Log.d("BottomSheet", "滑动偏移量:$slideOffset")
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> Log.d("BottomSheet", "完全展开")
BottomSheetBehavior.STATE_COLLAPSED -> Log.d("BottomSheet", "折叠")
BottomSheetBehavior.STATE_HIDDEN -> Log.d("BottomSheet", "隐藏")
BottomSheetBehavior.STATE_HALF_EXPANDED -> Log.d("BottomSheet", "半展开")
else -> Log.d("BottomSheet", "其他状态:$newState")
}
}
})
}
private fun initClick() {
btnExpand.setOnClickListener {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
btnCollapse.setOnClickListener {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
btnHide.setOnClickListener {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
btnClose.setOnClickListener {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
}
private fun initRecyclerView() {
// 示例:为 RecyclerView 设置简单适配器
recyclerView.adapter = SimpleAdapter(getData())
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)
}
// 模拟数据
private fun getData(): List<String> {
val list = mutableListOf<String>()
for (i in 1..20) {
list.add("列表项 $i")
}
return list
}
// dp 转 px
private fun dp2px(dp: Int): Int {
val density = resources.displayMetrics.density
return (dp * density + 0.5f).toInt()
}
// 简单适配器
inner class SimpleAdapter(private val data: List<String>) :
androidx.recyclerview.widget.RecyclerView.Adapter<SimpleAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View) :
androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) {
val tv: TextView = itemView as TextView
}
override fun onCreateViewHolder(parent: android.view.ViewGroup, viewType: Int): ViewHolder {
val tv = TextView(parent.context)
tv.layoutParams = androidx.recyclerview.widget.RecyclerView.LayoutParams(
androidx.recyclerview.widget.RecyclerView.LayoutParams.MATCH_PARENT,
dp2px(40)
)
tv.padding = dp2px(8)
return ViewHolder(tv)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tv.text = data[position]
}
override fun getItemCount(): Int = data.size
}
}
七、总结
BottomSheetBehavior 是实现底部面板的核心组件,核心要点:
- 必须依赖 Material Design 库,且父布局为
CoordinatorLayout; - 通过
from()方法获取 Behavior 实例,控制面板状态; - 常用状态:
STATE_EXPANDED(展开)、STATE_COLLAPSED(折叠)、STATE_HIDDEN(隐藏); - 可通过
peekHeight、isHideable、isFitToContents等配置自定义行为; - 利用
BottomSheetCallback监听状态变化,实现业务联动。
适用于底部菜单、筛选面板、详情弹窗、地图底栏等场景,是 Android 开发中高频使用的组件。