文章目录
前言
在 Android 应用开发中,BottomSheetDialog 是 Material Design 体系下高频使用的底部弹窗组件,凭借滑动折叠、半屏展示、沉浸式交互等特性,广泛应用于筛选面板、操作菜单、详情弹窗等场景。但实际开发中,开发者常面临三类核心问题:
- 折叠状态与高度失控:默认的 BottomSheetDialog 折叠高度(peekHeight)适配性差,不同屏幕(如刘海屏、折叠屏)下高度计算错误,无法精准实现「半屏展开」「自定义折叠高度」;
- 多状态切换不灵活:需要同时支持「折叠(最小高度)」「半展开(中间高度)」「全屏(最大高度)」三种状态,但官方 API 未提供直接的多状态配置方案;
- 底部按钮跟随滑动:弹窗底部的确认 / 取消按钮会随面板一起拖动,破坏交互逻辑,需要实现「面板滑动时按钮固定在底部」的效果。
针对以上痛点,本文将从实际开发场景出发,结合 Android 全版本屏幕尺寸适配方案,详细讲解 BottomSheetDialog 的核心定制技巧:包括基于屏幕可用高度精准设置折叠 / 半展开高度、通过 BottomSheetBehavior 实现多状态切换、以及底部固定按钮的布局与交互方案。
一、基础使用方法

kotlin
activity_main.xml页面:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dialog_recyclerview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
<Button
android:id="@+id/dialog_btn"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="底部固定按钮"
/>
</LinearLayout>
activity页面:
class MainActivity : BaseActivity() {
private lateinit var textBtn: Button
val data = mutableListOf<String>("这是一条数据","这是一条数据","这是一条数据","这是一条数据","这是一条数据")
override fun setViewId(): Int = R.layout.activity_main
override fun initView() {
textBtn = findViewById(R.id.text_btn)
}
override fun initListener() {
textBtn.setOnClickListener {
setBottomSheetDialog()
}
}
/**
* @describe: 设置BottomSheetDialog的相关内容
* @params:
* @return:
*/
private fun setBottomSheetDialog(){
//创建BottomSheetDialog
//获取dialog布局的view
val view = LayoutInflater.from(this@MainActivity).inflate(R.layout.dialog_text,null)
//初始化dialog对象
val bottomSheetDialog = BottomSheetDialog(this@MainActivity)
//设置dialog布局
bottomSheetDialog.setContentView(view)
//展示
bottomSheetDialog.show()
//设置bottomSheetDialog中recyclerView的相关内容
setRecyclerView(view)
}
/**
* @describe: bottomSheetDialog中展示数据的RecyclerView的相关设置
* @params:
* @return:
*/
private fun setRecyclerView(view: View) {
val recyclerView = view.findViewById<RecyclerView>(R.id.dialog_recyclerview)
recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
recyclerView.adapter = RecyclerViewAdapter1(data){
Toast.makeText(this@MainActivity,"当前点击了条目$it", Toast.LENGTH_SHORT).show()
}
}
}
adapter页面:
class RecyclerViewAdapter1(private val data:List<String>,private val onClick:(Int) -> Unit): RecyclerView.Adapter<RecyclerViewAdapter1.RecyclerViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_recyclerview1,parent,false)
return RecyclerViewHolder(view)
}
override fun onBindViewHolder(
holder: RecyclerViewHolder,
position: Int
) {
holder.bind(position,onClick)
}
override fun getItemCount(): Int = data.size
inner class RecyclerViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
private val itemView = itemView.findViewById<TextView>(R.id.item_text)
fun bind(position: Int, onClick: (Int) -> Unit){
itemView.text = data[position]
itemView.setOnClickListener {
onClick(position) //执行作为函数类型的函数体
}
}
}
}
二、不同场景下的设置
下面所用的获取全屏高度工具类如下:
Android中获取当前设备的宽高与屏幕密度等数据的工具类
点击后展开为全屏状态

kotlin
其他代码与上面相同,只改动bottomSheetDialog 属性:
val view = LayoutInflater.from(this@MainActivity).inflate(R.layout.dialog_text,null)
//初始化dialog对象
val bottomSheetDialog = BottomSheetDialog(this@MainActivity)
//设置dialog布局
bottomSheetDialog.setContentView(view)
//获取底部可活动面板的布局FrameLayout
val frameLayout = bottomSheetDialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)!!
//⭐关键:设置可活动面板高度为全屏
frameLayout.layoutParams.height = ScreenSizeUtil.getAppUsableSize(this@MainActivity).second //通过上面工具类获取全屏高度
//获取FrameLayout的behavior
val behavior = BottomSheetBehavior.from<FrameLayout>(frameLayout)
//通过behavior设置具体属性
behavior.apply {
//设置展示时的初始状态为展开
state = BottomSheetBehavior.STATE_EXPANDED //不设置此状态则展开为折叠高度,拖动最高为全屏
//设置折叠态的高度
peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_view_height)
}
//展示
bottomSheetDialog.show()
点击后展开为半屏状态

kotlin
private fun setBottomSheetDialog(){
//创建BottomSheetDialog
//获取dialog布局的view
val view = LayoutInflater.from(this@MainActivity).inflate(R.layout.dialog_text,null)
//初始化dialog对象
val bottomSheetDialog = BottomSheetDialog(this@MainActivity)
//设置dialog布局
bottomSheetDialog.setContentView(view)
//获取底部可活动面板的布局FrameLayout
val frameLayout = bottomSheetDialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)!!
//设置可活动面板高度为全屏
frameLayout.layoutParams.height = ScreenSizeUtil.getAppUsableSize(this@MainActivity).second //通过上面工具类获取全屏高度
//获取FrameLayout的behavior
val behavior = BottomSheetBehavior.from<FrameLayout>(frameLayout)
//通过behavior设置具体属性
behavior.apply {
//⭐关键:设置展示时的初始状态为半展开
state = BottomSheetBehavior.STATE_HALF_EXPANDED
halfExpandedRatio = 0.5f //配合状态调节半展开比例,0<取值<1,通常配置全屏高度设置
//设置折叠态的高度
peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_view_height)
}
//展示
bottomSheetDialog.show()
//设置bottomSheetDialog中recyclerView的相关内容
setRecyclerView(view)
}
注:
1.半折叠的状态必须配合 frameLayout.layoutParams.height为全屏高度才会有上面的效果,如果没有设置面板会默认为自适应高度,此时面板出现位置过高会如下图出现底部变成透明样子;
2.halfExpandedRatio 可以在0到1之间自由调节,但是不能为0或1,通过该数值可以自由调节展开状态的高度比例;
3.当前设置仅仅会在展开时出现目标高度,来回拖动时只会在全屏跟折叠高度之间停留,如果需要在目标高度进行停留时可参考下面的三态切换。
点击后展开为折叠状态
设置了面板高度为全屏高度时:

kotlin
//设置可活动面板高度为全屏
frameLayout.layoutParams.height = ScreenSizeUtil.getAppUsableSize(this@MainActivity).second //通过上面工具类获取全屏高度
//获取FrameLayout的behavior
val behavior = BottomSheetBehavior.from<FrameLayout>(frameLayout)
//通过behavior设置具体属性
behavior.apply {
//⭐关键:设置展示时的初始状态为折叠态,不设置全屏高度时最高只能拖动面板自适应高度
state = BottomSheetBehavior.STATE_COLLAPSED
//设置折叠态的高度
peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_view_height)
}
没有设置面板高度为全屏高度时:
kotlin
val behavior = BottomSheetBehavior.from<FrameLayout>(frameLayout)
//通过behavior设置具体属性
behavior.apply {
//设置展示时的初始状态为折叠态,不设置全屏高度时最高只能拖动面板自适应高度
state = BottomSheetBehavior.STATE_COLLAPSED
//设置折叠态的高度
peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_view_height)
}
点击后展开为半屏状态且可以在全屏、半屏、折叠之间三态切换

kotlin
//⭐关键:设置可活动面板高度为全屏
frameLayout.layoutParams.height =
ScreenSizeUtil.getAppUsableSize(this@MainActivity).second //通过上面工具类获取全屏高度
//获取FrameLayout的behavior
val behavior = BottomSheetBehavior.from<FrameLayout>(frameLayout)
//通过behavior设置具体属性
behavior.apply {
//⭐关键:设置展示时的初始状态为半展开
state = BottomSheetBehavior.STATE_HALF_EXPANDED
halfExpandedRatio = 0.5f //半展开比例
isHideable = true // 允许下拉隐藏
skipCollapsed = false // 不跳过折叠态
//设置折叠态的高度
peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_view_height)
isFitToContents = false //⭐关键:高度不由内容决定
}
全屏态、半屏态、折叠态之间两两组合实现两态切换
1.全屏态与半屏态切换:

用折叠态高度充当半屏态,其余设置与基础设置相同
kotlin
//⭐关键:设置可活动面板高度为全屏
frameLayout.layoutParams.height =
ScreenSizeUtil.getAppUsableSize(this@MainActivity).second //通过上面工具类获取全屏高度
//通过behavior设置具体属性
behavior.apply {
isHideable = true // 允许下拉隐藏,false时折叠态无法继续向下拖动
skipCollapsed = false //⭐关键:是否允许跳过折叠态
//设置展示时的初始状态为折叠态
state = BottomSheetBehavior.STATE_COLLAPSED
//⭐关键:设置折叠态的高度为全屏的一半
peekHeight = ScreenSizeUtil.getAppUsableSize(this@MainActivity).second / 2
isFitToContents = false //高度不由内容决定
}
2.全屏态与折叠态切换:

本质上与前面一样,只要折叠高度设置低一点
kotlin
//⭐关键:设置可活动面板高度为全屏
frameLayout.layoutParams.height =
ScreenSizeUtil.getAppUsableSize(this@MainActivity).second //通过上面工具类获取全屏高度
//通过behavior设置具体属性
behavior.apply {
isHideable = true // 允许下拉隐藏,false时折叠态无法继续向下拖动
skipCollapsed = false //⭐关键:是否允许跳过折叠态
//设置展示时的初始状态为折叠态
state = BottomSheetBehavior.STATE_COLLAPSED
//⭐关键:设置折叠态的高度
peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_view_height)
//isFitToContents = false //高度不由内容决定
}
3.半屏态与折叠态切换:

通过设置面板高度为屏幕一半来限制半屏展示,且必须不设置isFitToContents = false才不会拖动面板到全屏
kotlin
//⭐关键:设置可活动面板高度为半屏
frameLayout.layoutParams.height =
ScreenSizeUtil.getAppUsableSize(this@MainActivity).second / 2//通过上面工具类获取全屏高度
//通过behavior设置具体属性
behavior.apply {
isHideable = true // 允许下拉隐藏,false时折叠态无法继续向下拖动
skipCollapsed = false //是否允许跳过折叠态
//⭐关键:设置展示时的初始状态为半屏
state = BottomSheetBehavior.STATE_HALF_EXPANDED//初始状态为折叠可设置STATE_COLLAPSED
//⭐关键:设置折叠态的高度
peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_view_height)
//isFitToContents = false //高度不由内容决定
}
点击后展开为半屏状态且上下拖动时面板底部的按钮固定不动,当向下拖动高度小于折叠态+按钮高度时才跟随向下

关键点在于对面板高度的设置,通过计算让面板高度在全屏到折叠高度+按钮高度之间拖动时跟随变化为可见区域的高度,这样就可以通过权重来改变recyclerView的高度从而使按钮呈现固定的效果
kotlin
创建该文件设置固定dp
res/values/demians.xml:
<resources>
<dimen name="bottom_view_height">100dp</dimen>
</resources>
BottomSheetDialog设置的完整内容:
/**
* @describe: 设置BottomSheetDialog的相关内容
* @params:
* @return:
*/
private fun setBottomSheetDialog() {
//创建BottomSheetDialog
//获取dialog布局的view
val view = LayoutInflater.from(this@MainActivity).inflate(R.layout.dialog_text, null)
val dialogBtn = view.findViewById<Button>(R.id.dialog_btn)
//初始化dialog对象
val bottomSheetDialog = BottomSheetDialog(this@MainActivity)
//设置dialog布局
bottomSheetDialog.setContentView(view)
//获取底部可活动面板的布局FrameLayout
val frameLayout =
bottomSheetDialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)!!
//获取FrameLayout的behavior
val behavior = BottomSheetBehavior.from<FrameLayout>(frameLayout)
//⭐关键:设置可活动面板高度为半屏,让底部按钮一开始就显示出来
frameLayout.layoutParams.height =
ScreenSizeUtil.getAppUsableSize(this@MainActivity).second / 2 //通过上面工具类获取全屏高度
//通过behavior设置具体属性
behavior.apply {
isHideable = true // 允许下拉隐藏,false时折叠态无法继续向下拖动
skipCollapsed = false //是否允许跳过折叠态
state = BottomSheetBehavior.STATE_HALF_EXPANDED//初始状态为折叠可设置STATE_COLLAPSED
peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_view_height)
isFitToContents = false //高度不由内容决定
}
//可移动的最高高度
val maxHeight = ScreenSizeUtil.getAppUsableSize(this@MainActivity).second
//可移动的最低高度
val minHeight = behavior.peekHeight
//通过面板的移动监听来动态改变面板的高度
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
//⭐关键:通过改变高度让按钮固定
val targetHeight = ((maxHeight - minHeight) * slideOffset + minHeight).toInt()
//如果希望底部按钮从头到尾都是固定的可以去掉该判断直接 frameLayout.layoutParams.height = targetHeight
if (targetHeight <= minHeight + dialogBtn.height) //目的是向下拖动到折叠高度+固定按钮高度时可以让按钮此时跟随一起向下
frameLayout.layoutParams.height = minHeight + dialogBtn.height
else
frameLayout.layoutParams.height = targetHeight
bottomSheet.requestLayout() //刷新
}
})
//展示
bottomSheetDialog.show()
//设置bottomSheetDialog中recyclerView的相关内容
setRecyclerView(view)
}
2.halfExpandedRatio 可以在0到1之间自由调节,但是不能为0或1,通过该数值可以自由调节展开状态的高度比例;