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

如何在无Activity/View/Window依赖的全局环境下检测设备是否处于手势导航模式并监听模式变化?

我刚好在做悬浮窗应用时遇到过一模一样的问题,给你分享几个适配思路,完全贴合你“不依赖Activity/View”的需求:

一、检测当前是否处于手势导航模式

方案1:读取系统设置(需特殊权限)

Android 10(API 29)及以上,系统把导航模式存在Settings.Secure.NAVIGATION_MODE里,值为NAVIGATION_MODE_GESTURAL(2)时就是手势导航。但要注意:普通应用无法直接获取READ_SECURE_SETTINGS权限,这个权限属于系统级权限,只有签名应用、系统应用或通过adb授予才能用。

如果你的应用能拿到这个权限,用Application Context就能实现:

import android.content.Context
import android.os.Build
import android.provider.Settings

fun isGestureNavigationEnabled(context: Context): Boolean {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
        return false // 低于Android10无手势导航
    }
    return try {
        val mode = Settings.Secure.getInt(
            context.contentResolver,
            Settings.Secure.NAVIGATION_MODE
        )
        mode == Settings.Secure.NAVIGATION_MODE_GESTURAL
    } catch (e: Exception) {
        // 无权限或读取失败时默认返回false
        false
    }
}

方案2:利用悬浮窗的WindowInsets(无额外权限,适合你的场景)

因为你的应用已经有悬浮UI(用SAW权限创建的Window),可以通过监听这个Window的WindowInsets来间接判断导航模式:手势导航下,系统导航栏的交互区域高度会远低于传统导航栏(比如很多设备手势导航的底部条只有24dp左右,传统导航栏是48dp)。

实现方式:在创建悬浮Window时,设置Window.Callback并监听onApplyWindowInsets:

import android.view.Window
import android.view.WindowInsets
import android.view.WindowManager

fun setupGestureDetectionForFloatingWindow(window: Window) {
    window.callback = object : Window.Callback by window.callback {
        override fun onApplyWindowInsets(window: Window, insets: WindowInsets): WindowInsets {
            val navigationBarHeight = insets.getInsets(WindowInsets.Type.navigationBars()).bottom
            val density = window.context.resources.displayMetrics.density
            val dpHeight = navigationBarHeight / density
            // 根据高度阈值判断:比如小于30dp则认为是手势导航
            val isGesture = dpHeight < 30
            // 这里可以保存isGesture状态,或者触发UI调整逻辑
            return super.onApplyWindowInsets(window, insets)
        }
    }
}

这个方法不需要额外权限,而且完全基于你已有的悬浮Window实现。


二、监听导航模式变化

方案1:ContentObserver(需权限)

如果能拿到READ_SECURE_SETTINGS权限,可以注册ContentObserver监听导航模式的变化:

import android.content.ContentObserver
import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.net.Uri

class NavigationModeObserver(
    private val context: Context,
    private val onModeChanged: (Boolean) -> Unit
) : ContentObserver(Handler(Looper.getMainLooper())) {

    override fun onChange(selfChange: Boolean, uri: Uri?) {
        super.onChange(selfChange, uri)
        if (uri == Settings.Secure.getUriFor(Settings.Secure.NAVIGATION_MODE)) {
            val isGesture = isGestureNavigationEnabled(context)
            onModeChanged(isGesture)
        }
    }

    fun register() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            context.contentResolver.registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.NAVIGATION_MODE),
                false,
                this
            )
        }
    }

    fun unregister() {
        context.contentResolver.unregisterContentObserver(this)
    }
}

// 在Application的onCreate里注册
// NavigationModeObserver(applicationContext) { isGesture ->
//     // 处理模式变化,比如调整悬浮UI大小
// }.register()

方案2:监听系统配置变化广播(无权限)

普通应用可以注册ACTION_CONFIGURATION_CHANGED广播,当系统导航模式变化时会触发这个广播,然后在回调里重新检测导航模式:

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build

class NavigationModeChangeReceiver(
    private val onModeChanged: (Boolean) -> Unit
) : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == Intent.ACTION_CONFIGURATION_CHANGED && context != null) {
            // 这里用方案1或方案2的检测方法判断当前模式
            val isGesture = isGestureNavigationEnabled(context) // 或用WindowInsets的判断逻辑
            onModeChanged(isGesture)
        }
    }
}

// 注册广播(用Application Context)
fun registerNavigationChangeReceiver(context: Context, receiver: NavigationModeChangeReceiver) {
    val filter = IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)
    context.registerReceiver(receiver, filter)
}

// 记得在合适的地方注销
// context.unregisterReceiver(receiver)

这个方法不需要任何特殊权限,而且完全不依赖Activity/View,非常适合普通悬浮窗应用。


额外提示

  1. 厂商兼容:部分国产厂商有自定义手势导航逻辑,可能和系统标准不一致,建议在主流设备上测试你的检测逻辑。
  2. 内存泄漏:注册ContentObserver或广播时,记得在应用退出时注销,避免内存泄漏(可以用WeakReference包装回调,或者在Application的onTerminate里注销)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 20:17:29