写过安卓脚本的人应该都有过这种体验:
脚本跑在手机上,你在电脑这头盯着 按键精灵助手的调试面板 看 TracePrint 的输出。一旦拔掉数据线、或者换成手机上直接跑,日志就彻底看不见了。脚本"跑飞了"你也不知道飞在哪一步、卡在哪个循环、找色失败了多少次。
常见的三种日志方案对比:
|-------------------|--------|-------|------------|----------------|
| 方案 | 实时性 | 需要连电脑 | 需要看文件 | 适合场景 |
| TracePrint 调试面板 | 实时 | ✅ 必须 | ❌ | 开发调试阶段 |
| Log.Open 写文件 | 事后 | ❌ | ✅ 要用文件管理器翻 | 长期运行记录 |
| 悬浮窗日志(本文) | 实时 | ❌ | ❌ | 真机跑脚本、无人值守 |
悬浮窗日志最大的好处是:脚本在手机上自己跑,日志就浮在屏幕上,随时能看;嫌挡视线就点一下最小化,缩成一个小图标。

先看效果与设计拆解
效果
- 屏幕左上角一个半透明深色浮窗,绿色字体滚动输出运行日志;
- 顶部有标题栏和「---」最小化按钮;
- 按住浮窗任意位置可以拖动,防止挡住脚本操作;
- 点「---」后缩成右下角一个 54×54 的小方块,点一下还原。
结构拆解
整个界面其实就是两个悬浮窗 + 若干控件:
日志浮窗 (240×320)
├── 标题栏 文字控件 / 深灰底 / 左右内缩 5px
├── 标题文字 文字控件 / 白色 "运行日志"
├── btn_min 按钮控件 / 右上角 "---" → 触发 LogMinimize
└── 日志内容 文字控件 / 亮绿 / 实时刷新
图标浮窗 (54×54) ← 最小化后显示
└── 图标文字 文字控件 / "日志"
整体点击 → 触发 LogRestore
这里有个很关键的设计选择 :最小化不是"隐藏控件",而是两个独立浮窗互相显示/隐藏。原因后面踩坑部分会讲。
完整源码(可直接复制运行)
下面这份代码可以直接贴进按键精灵移动版,把
FindColor那段换成你自己的业务逻辑即可。 文件扩展名保存为.mq。
// ================================================================
// 按键精灵移动版(MQ / Q语法)
// 功能:可拖动、可最小化的【运行日志悬浮窗】
// 环境:安卓 root / 无障碍(FW 系列命令最低客户端 3.3.8,无障碍 5.0.0)
// ----------------------------------------------------------------
// ⚠️ 核心约束:FW 的 SetOn* 事件函数内部【无法访问外部变量】,
// 因此 LogMinimize / LogRestore 里的浮窗名必须写成字符串字面量,
// 不能引用 FW_LOG、FW_ICON 这些全局变量。
// ================================================================
// ---------------- 全局配置(仅在主流程与普通函数中使用) ----------------
Dim FW_LOG = "日志浮窗" ' 主日志浮窗名
Dim FW_ICON = "图标浮窗" ' 最小化后的小图标浮窗名
Dim TV_LOG = "日志内容" ' 日志正文控件名
Dim LBL_ICON = "图标文字" ' 小图标上的文字控件名
Dim LOG_X = 10, LOG_Y = 60 ' 主浮窗初始坐标
Dim ICO_X = 0, ICO_Y = 300 ' 图标浮窗初始坐标
Dim LOG_W = 240, LOG_H = 320 ' 主浮窗宽高
Dim ICO_W = 54, ICO_H = 54 ' 图标浮窗宽高
Dim LOG_MAX = 12 ' 日志窗最多保留的行数
Dim LOG_BUF = "" ' 日志文本缓冲
Dim NL = Chr(10) ' 换行符(若你的版本不换行,改成 Chr(13) & Chr(10))
// ================================================================
// 一、初始化:创建两个浮窗(全脚本只调用一次)
// ================================================================
Function LogInit()
' ---------- 1. 主日志浮窗 ----------
FW.NewFWindow FW_LOG, LOG_X, LOG_Y, LOG_W, LOG_H
FW.SetBackColor FW_LOG, "3A3A3A" ' 16进制颜色,格式为 BBGGRR
FW.Opacity FW_LOG, 85 ' 整体不透明度:0 全透明 ~ 100 不透明
FW.IsTouchMove FW_LOG, 1 ' 允许整窗拖动
FW.SetSmoothing FW_LOG, 5 ' 圆角
' 标题栏:左右各内缩 5px,绕开浮窗圆角,避免方形控件把圆角盖成直角
FW.AddTextView FW_LOG, "标题栏", "", 5, 5, LOG_W - 10, 24
FW.SetBackColor "标题栏", "2B2B2B"
FW.AddTextView FW_LOG, "标题文字", "运行日志", 12, 8, LOG_W - 60, 20
FW.SetTextColor "标题文字", "FFFFFF"
FW.SetTextSize "标题文字", 10
' 最小化按钮(右上角)
FW.AddButton FW_LOG, "btn_min", "---", LOG_W - 30, 8, 22, 20
FW.SetBackColor "btn_min", "2B2B2B"
FW.SetTextColor "btn_min", "FFFFFF"
FW.SetOnClick "btn_min", LogMinimize
' 日志正文
FW.AddTextView FW_LOG, TV_LOG, "", 10, 36, LOG_W - 20, LOG_H - 46
FW.SetTextColor TV_LOG, "0FE7A2" ' BBGGRR → RGB(162,231,15) 亮绿
FW.SetTextSize TV_LOG, 10
FW.Show FW_LOG
' ---------- 2. 小图标浮窗(点击还原日志) ----------
FW.NewFWindow FW_ICON, ICO_X, ICO_Y, ICO_W, ICO_H
FW.SetBackColor FW_ICON, "3A3A3A"
FW.SetSmoothing FW_ICON, 5
FW.IsTouchMove FW_ICON, 1
' 关键:点击事件绑在【悬浮窗本身】,不再额外套一个按钮
' 否则按钮会吃掉触摸事件,导致"能拖动浮窗、拖不动按钮"
FW.SetOnClick FW_ICON, LogRestore
FW.AddTextView FW_ICON, LBL_ICON, "日志", 0, 0, ICO_W, ICO_H
FW.SetTextColor LBL_ICON, "0FE7A2"
FW.SetTextSize LBL_ICON, 10
End Function
// ================================================================
// 二、最小化 / 还原(悬浮窗事件函数)
// ⚠️ 事件函数内读不到外部变量,所以这里的名字只能写字面量
// ================================================================
Function LogMinimize()
FW.Hide "日志浮窗" ' 隐藏大窗
FW.Show "图标浮窗" ' 显示小图标
End Function
Function LogRestore()
FW.Hide "图标浮窗" ' 隐藏小图标
FW.Show "日志浮窗" ' 恢复大窗
End Function
// ================================================================
// 三、写日志(核心,普通函数,可以正常使用全局变量)
// 参数:text ------ 一行日志内容
// 说明:自动加时间戳;超过 LOG_MAX 行自动丢弃最旧的,模拟滚动效果
// ================================================================
Function LogAdd(text)
LOG_BUF = LOG_BUF & "[" & LogStamp() & "] " & text & NL
LOG_BUF = LogKeepLast(LOG_BUF, LOG_MAX)
FW.SetText TV_LOG, LOG_BUF ' FW.SetText 需要客户端 3.5.1 及以上
End Function
' 只保留最后 n 行
Function LogKeepLast(s, n)
Dim arr = Split(s, NL)
Dim total = Len(arr)
Dim i, out
If total <= n Then
LogKeepLast = s
Exit Function
End If
out = ""
For i = total - n + 1 To total
out = out & arr[i] & NL
Next
LogKeepLast = out
End Function
' 时:分:秒(由设备系统时间戳换算,用作日志前缀)
Function LogStamp()
Dim sec = Time() Mod 86400
LogStamp = Pad0(sec \ 3600) & ":" & Pad0((sec Mod 3600) \ 60) & ":" & Pad0(sec Mod 60)
End Function
' 个位数补零
Function Pad0(v)
If v < 10 Then
Pad0 = "0" & v
Else
Pad0 = CStr(v)
End If
End Function
// ================================================================
// 四、主流程演示:循环找色,把每次结果实时写进日志悬浮窗
// ================================================================
LogInit
LogAdd "脚本启动,开始监听目标颜色"
Dim intX, intY, count = 0
Do
count = count + 1
' 0,0,0,0 表示全屏范围;实际使用时换成你自己的目标颜色与容差
FindColor 0, 0, 0, 0, "FFFFFF", 0, 0.9, intX, intY
If intX > -1 Then
LogAdd "第" & count & "次:找到目标 (" & intX & "," & intY & "),执行点击"
Tap intX, intY
Else
LogAdd "第" & count & "次:未找到目标,1 秒后重试"
End If
Delay 1000
Loop
' 脚本停止后,悬浮窗会自动消失,无需手动清理
核心代码逐段拆解
1. 创建浮窗:FW.NewFWindow
|----------------|--------|-------------|
| 参数 | 类型 | 说明 |
| fwName | string | 悬浮窗名称(全局唯一) |
| leftX / topY | number | 屏幕坐标,默认 0 |
| width / height | number | 宽高,省略则自适应内容 |
两个必须知道的点:
- 一个脚本最多创建 32 个悬浮窗;
- 脚本停止后浮窗会自动消失,不用手动清理。
2. 添加子控件
注意 leftX / topY 是相对于父控件(这里是浮窗)左上角的坐标,不是屏幕绝对坐标。整个浮窗内部就是一个简易的相对布局系统。
3. 外观设置
|-----------|--------------------------------------|-------------------|
| 目的 | 命令 | 说明 |
| 浮窗背景色 | FW.SetBackColor(名称, "3A3A3A") | 16 进制,格式 BBGGRR |
| 整体不透明度 | FW.Opacity(名称, 85) | 0 全透明 ~ 100 不透明 |
| 圆角 | FW.SetSmoothing(名称, 5) | 数值越大越圆 |
| 允许拖动 | FW.IsTouchMove(名称, 1) | 1 允许 / 0 禁止 |
| 文字颜色 / 字号 | FW.SetTextColor / FW.SetTextSize | 按控件设置 |
| 运行时改文字 | FW.SetText(eleName, text) | 就是靠它刷新日志 |
4. 写日志的核心:LogAdd
原文的浮窗里塞的是一段固定文字,看着像日志,其实不会变。真正的日志系统必须能动态追加 ,靠的就是 FW.SetText:
三个细节:
- 加时间戳,方便定位"卡在哪一秒";
- 只保留最后 N 行:文字控件高度是固定的,行数超了不会自动滚动,手动裁掉旧行比什么都简单可靠;
- 用字符串拼接缓冲 ,不要用 table 去做队列------按键的 table 是可以
t[Len(t)+1] = v追加,但每次都要重排、转字符串,不如直接拼字符串来得直观。
业务代码里只要这样调用:
If intX > -1 Then
LogAdd "第" & count & "次:找到目标 (" & intX & "," & intY & ")"
Else
LogAdd "第" & count & "次:未找到目标,1 秒后重试"
End If
【日志窗里滚动的效果,能看到时间戳前缀】

踩坑记录(这部分最重要)
坑 1(最隐蔽):悬浮窗事件函数里读不到全局变量
这是这次最大的收获。MQ 的官方语法说明里明确写着:
在悬浮窗控件的事件函数(如
SetOnClick这类以SetOn开头的事件)中,无法访问外部变量,只能使用事件函数内部的局部变量。
也就是说,你下面这么写是无效的:
Dim FW_LOG = "日志浮窗"
Function LogMinimize()
FW.Hide FW_LOG ' ❌ FW_LOG 在这里是 null,浮窗名传进去是空的
End Function
必须写成字面量:
Function LogMinimize()
FW.Hide "日志浮窗" ' ✅ 直接写名字
FW.Show "图标浮窗"
End Function
所以浮窗名、控件名建议一次定好,之后当成"常量"用字符串直接写。 这也解释了为什么很多示例代码里事件函数中的名字都是硬编码的------不是作者懒得抽变量,是这套机制不允许。
顺带一提:普通函数(比如上面的
LogAdd)不受这个限制 ,全局变量照常用。受限的只有FW.SetOn*绑定的事件函数。
坑 2:顶部标题栏会把浮窗的圆角切成直角
现象:浮窗设了 FW.SetSmoothing,但顶部那条深色标题栏是方形的,直接把上面两个圆角盖住了。
原因很简单:子控件永远是矩形,绘制在浮窗之上,不会继承浮窗的圆角裁剪。 只要控件贴着左右边缘且铺满颜色,圆角就一定被遮。
两个解决思路:
- 方案 A(本文用的) :标题栏不贴边,左右各内缩 5px(
leftX = 5, width = 浮窗宽 - 10),同时把标题栏和浮窗的底色设成很接近的深灰(2B2B2B/3A3A3A),视觉上几乎看不出那 5px 的接缝;
- 方案 B:干脆不给标题栏设背景色,标题只用一个文字控件,靠浮窗自身的背景色撑住。
如果非要"完美圆角 + 实心标题栏",那得靠两张带圆角的图片去贴,成本不划算。
坑 3:小图标上的按钮点得动、却拖不动
现象:主浮窗能随便拖,但最小化后那个小图标怎么拖都不动。
原因:FW.IsTouchMove 是作用在悬浮窗 这一层的;而你在浮窗里放了一个铺满的按钮,触摸落在按钮上时事件被子控件消费掉了,浮窗根本收不到拖动。
修复:小图标不要套按钮,直接把点击事件绑在悬浮窗本身上 。FW.SetOnClick 的第一个参数支持"悬浮窗或控件名称",所以这样写是合法的:
FW.NewFWindow "图标浮窗", 0, 300, 54, 54
FW.IsTouchMove "图标浮窗", 1 ' 整窗可拖
FW.SetOnClick "图标浮窗", LogRestore ' 整窗可点
这样拖动和点击都在同一层级,不会互相吞事件。
如果实测发现你的版本上"整窗点击"没能触发,退一步的稳妥做法是:保留按钮但别让它铺满,浮窗做成 70×70、按钮只占中间 46×46,留出一圈可拖动的边缘。
坑 4:颜色是 BBGGRR,不是 RRGGBB
这条坑过无数人。FW 系列命令里的颜色字符串,格式是 BBGGRR (和按键精灵取色命令一致),不是网页/CSS 里的 RRGGBB。
对照一下就明白了:
|--------|----------|----------------------------|
| 颜色 | FW 里应写 | 说明 |
| 红色 | 0000FF | B=00, G=00, R=FF |
| 纯绿 | 00FF00 | 这次 G=FF |
| 白色 | FFFFFF | 顺序无所谓 |
| 深灰 | 3A3A3A | 顺序无所谓 |
| 本文的日志绿 | 0FE7A2 | B=0F, G=E7, R=A2 → 实际显示为亮绿 |
所以灰色系随便写,彩色一定要换算 。你从取色器拿到的 RRGGBB,要反过来写成 BBGGRR 才会显示成你想要的颜色。
坑 5:FW.Hide 和 FW.Close 不是一回事
FW.Hide------ 隐藏 ,之后FW.Show还能显示回来(最小化要用这个);
FW.Close------ 关闭并销毁 ,销毁后的浮窗不能再FW.Show,必须重新FW.NewFWindow。
很多人做最小化用错了 Close,结果点还原直接没反应。
这套悬浮窗日志我现在的用法是把它当成**脚本的""仪表盘""**∶循环次数、找色成功/失败、超时重试、当前状态全部实时打上去。脚本在手机上一跑几小时,出了什么问题扫一眼浮窗就知道,比事后翻日志文件快得多。
代码里两个关键点再强调一遍,别的都可以忘:│
1.**Fw的'Seton*'事件函数读不到全局变量**---浮窗名---律写字面量;
2.**颜色是`BBGGRR'不是'RRGGBB~**------彩色必须换算。
如果这篇对你有帮助,点个赞收藏一下。有任何关于按键精灵移动端开发安卓版的问题,或者想看"悬浮窗日志+多线程"的做法,欢迎评论区交流。