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

Flutter中如何检测窗口浮层下方的文本?(dash bubble场景)

悬浮窗下方文本检测与操作实现指导

1. 先搞定必要权限

  • 必须申请ACCESSIBILITY_SERVICE无障碍权限,这是Android允许读取其他APP界面内容的唯一合法途径,普通APP直接读其他APP内容会被系统拦截。
  • 另外确认悬浮窗权限SYSTEM_ALERT_WINDOW已经开启(Dash Bubble一般会帮你处理,但最好在代码里做个检查)。

2. 写个无障碍服务核心逻辑

  • 继承AccessibilityService,重写onAccessibilityEvent方法,用来处理界面元素的获取。当悬浮窗移动到目标位置后,触发这个服务去抓对应坐标的文本。
  • 核心代码示例(Kotlin):
override fun onAccessibilityEvent(event: AccessibilityEvent) {
    val rootNode = rootInActiveWindow ?: return
    // 拿到悬浮窗的全局坐标(假设你的悬浮窗视图是bubbleView)
    val rect = Rect()
    bubbleView.getGlobalVisibleRect(rect)
    // 取悬浮窗中心作为检测点,比边缘更准确
    val checkX = rect.centerX()
    val checkY = rect.centerY()
    // 遍历节点树找对应位置的文本节点
    val targetNode = findTextNodeAtPos(rootNode, checkX, checkY)
    targetNode?.text?.let { text ->
        // 执行复制操作,把文本存到剪贴板
        val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
        val clipData = ClipData.newPlainText("CopiedText", text)
        clipboard.setPrimaryClip(clipData)
    }
}

// 递归遍历节点,找到坐标对应的文本节点
private fun findTextNodeAtPos(node: AccessibilityNodeInfo, x: Int, y: Int): AccessibilityNodeInfo? {
    if (node.contains(x, y) && node.text != null) {
        return node
    }
    for (i in 0 until node.childCount) {
        val child = node.getChild(i) ?: continue
        val result = findTextNodeAtPos(child, x, y)
        if (result != null) return result
    }
    return null
}

3. 把悬浮窗移动和检测绑定起来

  • 监听Dash Bubble的拖动事件,比如在悬浮窗的OnTouchListener里,当用户抬手(ACTION_UP)时,通知无障碍服务去执行检测逻辑。
  • 如果Dash Bubble有自带的移动完成回调,直接在回调里触发检测就行,不用自己写触摸监听。

4. 适配WhatsApp这类特定APP

  • 不同APP的UI结构不一样,比如WhatsApp的聊天文本有特定的视图ID,你可以通过这个ID精准定位,避免抓到无关文本。
  • 比如WhatsApp的聊天消息文本ID是com.whatsapp:id/message_text,可以修改遍历逻辑:
private fun findTextNodeAtPos(node: AccessibilityNodeInfo, x: Int, y: Int): AccessibilityNodeInfo? {
    // 优先匹配WhatsApp的文本节点
    if (node.viewIdResourceName == "com.whatsapp:id/message_text" && node.contains(x, y)) {
        return node
    }
    // 通用匹配逻辑
    if (node.contains(x, y) && node.text != null) {
        return node
    }
    for (i in 0 until node.childCount) {
        val child = node.getChild(i) ?: continue
        val result = findTextNodeAtPos(child, x, y)
        if (result != null) return result
    }
    return null
}

5. 优化用户体验

  • 第一次启动时,引导用户去系统设置开启无障碍权限,这个权限必须手动开,没法代码自动申请。
  • 悬浮窗拖动停止后,可以加个短暂的Toast提示,比如“已复制文本”,让用户知道操作成功。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 04:20:21