You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于BottomSheetDialogFragment实现非阻塞式底部弹窗?

基于BottomSheetDialogFragment实现非阻塞底部弹窗需求

问题背景

需要实现一款满足以下要求的底部弹窗:

  • 圆角样式
  • 固定高度
  • 不可拖拽
  • 内容可滚动
  • 点击非弹窗区域隐藏弹窗
  • 按返回键隐藏弹窗
  • 非阻塞式:点击非弹窗区域时,弹窗隐藏且被点击元素获得焦点

使用BottomSheetBehavior可实现所有需求,但会增加Activity布局复杂度且需手动处理触摸事件;BottomSheetDialogFragment代码更简洁,但无法实现第7点——点击外部EditText时弹窗隐藏,但EditText无法获焦。


解决方案

核心思路是拦截BottomSheetDialog的触摸事件,记录弹窗外部被点击的View,在弹窗隐藏后主动为该View请求焦点。以下是具体实现步骤:

1. 自定义BottomSheetDialogFragment

重写onCreateDialog方法,配置弹窗属性并拦截触摸事件:

class CustomBottomSheetDialogFragment : BottomSheetDialogFragment() {

    private var clickedOutsideView: View? = null

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 加载弹窗内容布局(包含ScrollView实现内容滚动)
        return inflater.inflate(R.layout.bottom_sheet_content, container, false)
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog

        // 配置弹窗核心属性
        dialog.behavior.apply {
            isDraggable = false // 禁用拖拽
            peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_sheet_fixed_height) // 设置固定高度
        }

        // 拦截触摸事件,记录外部点击的View
        dialog.window?.decorView?.setOnTouchListener { v, event ->
            if (event.action == MotionEvent.ACTION_UP) {
                val sheetView = dialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
                sheetView?.let { sheet ->
                    val location = IntArray(2)
                    sheet.getLocationOnScreen(location)
                    val sheetLeft = location[0]
                    val sheetTop = location[1]
                    val sheetRight = sheetLeft + sheet.width
                    val sheetBottom = sheetTop + sheet.height

                    // 判断点击位置是否在弹窗外部
                    if (event.rawX < sheetLeft || event.rawX > sheetRight ||
                        event.rawY < sheetTop || event.rawY > sheetBottom) {
                        // 根据坐标找到被点击的外部View
                        clickedOutsideView = activity?.window?.decorView?.findViewAtPosition(
                            event.rawX.toInt(),
                            event.rawY.toInt()
                        )
                    }
                }
            }
            // 传递触摸事件,保证原有逻辑正常执行
            v.onTouchEvent(event)
        }

        // 监听弹窗隐藏事件,为外部View请求焦点
        dialog.setOnDismissListener {
            clickedOutsideView?.requestFocus()
            clickedOutsideView = null
        }

        return dialog
    }
}

2. 添加坐标找View的工具函数

扩展View类,实现根据屏幕坐标查找对应View的功能:

fun View.findViewAtPosition(x: Int, y: Int): View? {
    val childCount = childCount
    for (i in 0 until childCount) {
        val child = getChildAt(i)
        val location = IntArray(2)
        child.getLocationOnScreen(location)
        val childLeft = location[0]
        val childTop = location[1]
        val childRight = childLeft + child.width
        val childBottom = childTop + child.height

        if (x in childLeft..childRight && y in childTop..childBottom) {
            // 递归查找子View,确保找到最底层的可点击View
            return child.findViewAtPosition(x - childLeft, y - childTop) ?: child
        }
    }
    return this
}

3. 配置弹窗圆角样式

在弹窗内容布局的根View中添加圆角背景:

bottom_sheet_content.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bottom_sheet_rounded_bg">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 可滚动的内容区域 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="示例内容1"
            android:textSize="18sp"/>
        <!-- 添加更多内容实现滚动效果 -->

    </LinearLayout>
</ScrollView>

drawable/bottom_sheet_rounded_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/white"/>
    <corners
        android:topLeftRadius="16dp"
        android:topRightRadius="16dp"/>
</shape>

原理说明

  1. 拦截触摸事件时,通过坐标判断点击位置是否在弹窗外部,找到对应的目标View并记录。
  2. 当弹窗关闭(onDismiss触发),主动调用requestFocus()让记录的外部View获得焦点,实现非阻塞的交互需求。
  3. 保留BottomSheetDialogFragment的简洁性,无需额外布局和复杂的触摸事件处理。

内容的提问来源于stack exchange,提问作者Cheok Yan Cheng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 16:00:58