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 开发中高频使用的组件。

相关推荐
帅得不敢出门2 小时前
Android Framework不弹窗设置默认sim卡
android·java·framework
技术摆渡人2 小时前
RK3588 边缘 AI 深度开发指南:从 Android NNAPI 源码到 LLM 大模型性能调优
android·人工智能
sinat_384241092 小时前
从零开始打造一个 Android 音乐播放器(Kotlin + Jetpack Compose)
android·开发语言·kotlin
cyw89982 小时前
手动安装mysql8.0.44
android
Edward.W2 小时前
Android (ADB)、iOS、OpenHarmony 常用调试命令对照表
android·ios·adb
Railshiqian2 小时前
DeepSeek反馈的,如何通过adb获取某个Window或View的绘制内容的方法
android·adb
消失的旧时光-19432 小时前
Android(Kotlin) ↔ Flutter(Dart) 的“1:1 对应表”:架构分层来对照(MVVM/MVI 都适用)
android·flutter·kotlin
Coffeeee2 小时前
Android15适配之edge-to-edge和16kb到底咋适配
android·前端·android studio
Java猿_11 小时前
Spring Boot 集成 Sa-Token 实现登录认证与 RBAC 权限控制(实战)
android·spring boot·后端