如何让自定义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
相关产品推荐
相关产品推荐

