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

如何让自定义View显示在BottomSheet/Dialog之上?

解决方案

因为BottomSheet和Dialog都是独立的Window实例,它们的窗口层级高于主Activity的View层级,所以直接放在Activity布局里的View无法覆盖它们。要实现全局覆盖的自定义通知,有两种可靠方案:

方案一:通过WindowManager直接添加全局View

这是最接近原生Toast行为的方案,将自定义通知View直接附加到系统窗口层级,确保能覆盖所有应用内的Window(包括Dialog、BottomSheet)。

步骤1:声明权限(Android 6.0+)

在AndroidManifest.xml中添加悬浮窗权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Android 6.0+需要动态申请该权限,在显示通知前检查权限:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (!Settings.canDrawOverlays(context)) {
        val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
        startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMISSION)
        return
    }
}

步骤2:封装通知管理类

创建一个单例的CustomNotificationManager,负责View的添加、移除和更新:

class CustomNotificationManager private constructor(private val context: Context) {
    private val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    private var notificationView: NotificationView? = null

    companion object {
        @Volatile
        private var instance: CustomNotificationManager? = null

        fun getInstance(context: Context) = instance ?: synchronized(this) {
            instance ?: CustomNotificationManager(context.applicationContext).also { instance = it }
        }
    }

    fun showNotification(message: String) {
        notificationView?.let { windowManager.removeView(it) }

        notificationView = NotificationView(context).apply {
            // 设置通知内容
            setMessage(message)
            // 设置点击关闭
            setOnClickListener { hideNotification() }
        }

        val params = WindowManager.LayoutParams().apply {
            // 设置窗口类型:Android 8.0+用TYPE_APPLICATION_OVERLAY,低版本用TYPE_PHONE
            type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                @Suppress("DEPRECATION")
                WindowManager.LayoutParams.TYPE_PHONE
            }
            // 设置布局参数
            width = WindowManager.LayoutParams.WRAP_CONTENT
            height = WindowManager.LayoutParams.WRAP_CONTENT
            gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
            // 设置边距
            y = context.resources.getDimensionPixelSize(R.dimen.notification_margin_bottom)
            // 设置窗口属性:透明背景,无焦点,不拦截触摸事件
            flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
            format = PixelFormat.TRANSLUCENT
        }

        notificationView?.let { windowManager.addView(it, params) }

        // 自动隐藏
        Handler(Looper.getMainLooper()).postDelayed({ hideNotification() }, 3000)
    }

    fun hideNotification() {
        notificationView?.let {
            windowManager.removeView(it)
            notificationView = null
        }
    }
}

步骤3:调用显示通知

在任意位置调用:

CustomNotificationManager.getInstance(context).showNotification("自定义通知内容")

方案二:用透明Dialog作为载体

如果不想申请悬浮窗权限,可以创建一个全屏透明的Dialog,将自定义NotificationView放在其中,通过设置Dialog的Window层级来覆盖其他Dialog和BottomSheet。

步骤1:创建透明Dialog布局

dialog_notification.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">

    <com.example.notification.NotificationView
        android:id="@+id/notification_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_marginBottom="@dimen/notification_margin_bottom"/>

</FrameLayout>

步骤2:封装Dialog通知类

class CustomNotificationDialog(context: Context) : Dialog(context, android.R.style.Theme_Translucent_NoTitleBar) {
    init {
        setContentView(R.layout.dialog_notification)
        val notificationView = findViewById<NotificationView>(R.id.notification_view)
        // 设置点击关闭
        notificationView.setOnClickListener { dismiss() }
        // 设置Window参数
        window?.apply {
            // 设置层级高于普通Dialog和BottomSheet
            setType(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                @Suppress("DEPRECATION")
                WindowManager.LayoutParams.TYPE_PHONE
            })
            setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT)
            setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
        }
    }

    fun setMessage(message: String) {
        findViewById<NotificationView>(R.id.notification_view).setMessage(message)
    }

    override fun show() {
        super.show()
        // 自动隐藏
        Handler(Looper.getMainLooper()).postDelayed({ dismiss() }, 3000)
    }
}

步骤3:调用显示

CustomNotificationDialog(context).apply {
    setMessage("自定义通知内容")
    show()
}

注意事项

  • 方案一需要悬浮窗权限,部分国产ROM可能需要手动开启权限;方案二不需要额外权限,但Dialog的层级设置在部分系统可能需要调整。
  • 两种方案都要确保在主线程操作WindowManager,避免ANR。
  • 如果需要支持多Activity,推荐用单例的CustomNotificationManager,确保全局只有一个通知实例。

内容的提问来源于stack exchange,提问作者Buntupana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 10:12:49