Android BottomSheetBehavior 使用详解

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

  • 解决方案:

    kotlin 复制代码
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        bottomSheetBehavior.isFitToContents = false
        bottomSheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
    }

3. 拖拽时与其他 View 冲突

  • 原因:嵌套滚动未处理,或多个 Behavior 冲突;
  • 解决方案:
    1. 禁用非目标 View 的拖拽:bottomSheetBehavior.isDraggable = false
    2. 为嵌套滚动 View 设置 app:layout_behavior="@string/appbar_scrolling_view_behavior"
    3. 重写 onInterceptTouchEvent 处理触摸事件。

4. 面板高度超出屏幕

  • 原因:面板内容高度过高,未设置 wrap_content
  • 解决方案:
    1. 面板根布局高度设为 wrap_content
    2. 内部使用滚动布局(如 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 是实现底部面板的核心组件,核心要点:

  1. 必须依赖 Material Design 库,且父布局为 CoordinatorLayout
  2. 通过 from() 方法获取 Behavior 实例,控制面板状态;
  3. 常用状态:STATE_EXPANDED(展开)、STATE_COLLAPSED(折叠)、STATE_HIDDEN(隐藏);
  4. 可通过 peekHeightisHideableisFitToContents 等配置自定义行为;
  5. 利用 BottomSheetCallback 监听状态变化,实现业务联动。

适用于底部菜单、筛选面板、详情弹窗、地图底栏等场景,是 Android 开发中高频使用的组件。

相关推荐
阿巴斯甜1 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95272 小时前
Andorid Google 登录接入文档
android
黄林晴4 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab16 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿19 小时前
Android MediaPlayer 笔记
android
Jony_19 小时前
Android 启动优化方案
android
阿巴斯甜20 小时前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇20 小时前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android