You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android Oreo中如何控制WindowManager悬浮窗层级优先级?

解决Android Oreo+下悬浮窗OV1始终在OV2之上的问题

针对你遇到的API 27之后TYPE_APPLICATION_OVERLAY无法通过窗口类型区分层级的问题,我有几个实用的解决方案,按推荐度排序:


方案1:自定义变暗背景(最推荐)

原来用FLAG_DIM_BEHIND会让整个屏幕变暗,包括OV1,本质是这个flag会在当前窗口下方添加一个全局的变暗层,层级比同类型的其他窗口高。换成自定义全屏半透明背景作为OV2的一部分,就能让变暗效果只在OV2窗口内,不会覆盖OV1。

步骤:

  1. 给OV2创建包含背景和子按钮的布局:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 自定义半透明背景,替代FLAG_DIM_BEHIND -->
    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#80000000" /> <!-- 透明度可调整,这里是50%黑 -->

    <!-- 子按钮容器,按需调整位置 -->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        android:orientation="vertical"
        android:background="#ffffff"
        android:padding="8dp">

        <Button android:id="@+id/btn_function1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="功能1"/>
        <!-- 更多子按钮... -->
    </LinearLayout>
</FrameLayout>
  1. 配置OV2的Window参数,移除FLAG_DIM_BEHIND
val ov2Params = WindowManager.LayoutParams().apply {
    type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
    width = WindowManager.LayoutParams.MATCH_PARENT
    height = WindowManager.LayoutParams.MATCH_PARENT
    gravity = Gravity.TOP or Gravity.LEFT
    flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    format = PixelFormat.TRANSLUCENT // 支持透明背景
}
  1. 确保OV1的窗口参数设置zOrderOnTop = true,或者在添加OV2之前重新添加OV1(后添加的同类型窗口层级更高)。

方案2:利用zOrder参数控制层级

在同一TYPE_APPLICATION_OVERLAY类型下,窗口的层级由添加顺序和zOrder参数共同决定:

  • 给OV1设置zOrderOnTop = true,让它强制处于同类型窗口的最上层
  • 给OV2设置zOrderOnTop = false,或者不设置(默认false)

代码示例:

// OV1的参数配置
val ov1Params = WindowManager.LayoutParams().apply {
    type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
    width = 100
    height = 100
    gravity = Gravity.TOP or Gravity.LEFT
    flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    zOrderOnTop = true // 关键:让OV1在同类型窗口最上层
}
windowManager.addView(ov1View, ov1Params)

// OV2的参数配置
val ov2Params = WindowManager.LayoutParams().apply {
    type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
    // 其他参数...
    zOrderOnTop = false // 不抢占最上层位置
    // 如果你还想用FLAG_DIM_BEHIND,需要确保OV1的zOrder优先级更高,但可能在部分机型上不稳定
}

⚠️ 注意:部分定制ROM可能对zOrder参数有特殊处理,可能出现层级异常,所以这个方案的兼容性不如方案1。


方案3:动态重新添加OV1

同类型窗口中,后添加的窗口层级更高。所以在弹出OV2之前,可以先移除OV1,再重新添加它,这样OV1就会在OV2之上:

// 点击OV1弹出OV2时的逻辑
ov1View.setOnClickListener {
    // 保存OV1当前的位置,避免重新添加后位置错乱
    val currentX = ov1Params.x
    val currentY = ov1Params.y

    // 移除OV1
    windowManager.removeView(ov1View)
    // 重新添加OV1,此时它的层级会高于之前的所有同类型窗口
    ov1Params.x = currentX
    ov1Params.y = currentY
    windowManager.addView(ov1View, ov1Params)

    // 再添加OV2
    windowManager.addView(ov2View, ov2Params)
}

这个方案的缺点是重新添加View会触发View的生命周期重新执行,如果你在OV1里有复杂的状态,需要额外处理状态保存和恢复。


总结下来,**方案1(自定义变暗背景)**是最稳定可靠的,既解决了层级问题,又避免了FLAG_DIM_BEHIND带来的全局覆盖问题,适配所有API27+的设备。

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

火山引擎 最新活动