这是一款仅限个人使用的 Android 悬浮式自动化工具「指尖分身」,使用Kotlin语言开发,兼容Android Studio。
面向使用者的完整操作步骤与常见问题见 user_guide.md。
这是一款基于 Android 无障碍服务的自动化工具,通过悬浮窗控制全局模拟点击、滑动和系统按键,支持多点顺序循环执行。应用名为「指尖分身」,内置三种可切换的工作模式(在主页分段开关切换并持久化,默认“连点器”):
| 模式 | 对应服务 | 工作方式 |
|---|---|---|
| 连点器(旧功能) | FloatingWindowService |
手动摆放编号点击点/滑动点,按顺序循环自动执行点击、滑动与系统按键 |
| 代点器(快捷点击) | QuickClickService |
为子按钮配置「目标文字」「模板图片」或「屏幕坐标」,点击时由无障碍服务定位(文本匹配 / 图像识别)或直接取坐标,再按动作类型(点击 / 双击 / 长按 / 滑动)注入手势,支持随机坐标偏移与脚本保存/加载;运行态下在子按钮上长按 / 滑动会透传给下层应用 |
| 录点器(录制 / 回放) | RecorderService |
全屏透明覆盖层录制真实触摸(点击 / 长按 / 滑动 / 间隔),保存为独立脚本后按录制节奏回放,支持暂停、重复次数与 0.5×~2× 倍速 |
三个模块互相完全独立:各自的服务、脚本存储、运行状态互不读写;共用通用悬浮窗框架(按钮样式、拖动吸边、原始触摸链路)与统一弹框组件。
主界面、悬浮窗与设置弹框使用 Jetpack Compose;代点器的全屏截图框选页、裁剪比例条、坐标拾取浮层与录点器的全屏录制捕获层使用原生 View(Service 浮层场景下窗口焦点、触摸拦截与生命周期更可控)。原 XML 布局已移除,仅保留图标、颜色等基础资源。
代码目录、分包职责与关键约束见 project_structure.md。
| 权限 | 用途 |
|---|---|
SYSTEM_ALERT_WINDOW |
显示全局悬浮窗 |
BIND_ACCESSIBILITY_SERVICE |
模拟点击、滑动、全局按键 |
FOREGROUND_SERVICE |
保持后台运行,防止被系统杀死 |
FOREGROUND_SERVICE_DATA_SYNC |
与三个悬浮窗服务声明的 foregroundServiceType="dataSync" 匹配(Android 14+ 必需) |
POST_NOTIFICATIONS |
Android 13+ 显示前台服务通知(通知栏「停止」入口),运行时申请 |
无障碍服务必须由用户在系统设置中手动开启。
⚠️ Android 14(API 34)起:前台服务必须声明与foregroundServiceType对应的类型权限,否则startForeground()抛SecurityException,表现为「点击启动悬浮窗后 App 闪退」。若要调整服务类型,需同步修改AndroidManifest.xml中的类型与类型权限。
⚠️ Android 15(API 35)起:dataSync类型前台服务有 6 小时 / 24 小时运行时长上限,超时后系统回调Service.onTimeout(),App 在几秒内主动停止服务(不会闪退),悬浮窗会随之消失。代点器的「图像识别」依赖无障碍
takeScreenshot,仅支持 Android 11(API 30)及以上;低版本上截图捕获入口置灰、图像匹配点击不可用,文本匹配、坐标点击与连点器不受影响。无障碍配置中已声明
android:canTakeScreenshot="true"。从旧版本升级后,如果图像识别提示截图失败,请在系统设置中关闭再重新开启本应用的无障碍服务,使新的能力声明生效。
布局:竖屏为垂直排列的按钮组 + 一个主操作按钮;横屏可用高度放不下时,展开栏会自动改为横向一排(见 3.10)。
主按钮功能:
- 点击:展开/折叠下方操作按钮区域
- 按住拖动:移动整个悬浮窗,松手后按当前方向吸附——竖排贴左 / 右边缘;横排(以及横屏下的折叠态)贴上 / 下边缘,水平位置保留松手处、不强制靠右;吸附位置落在安全区内(避让状态栏、导航栏、挖孔),距边保留 12dp 内边距
操作按钮列表(展开后显示):
| 按钮 | 功能 | 特殊行为 |
|---|---|---|
| 启动/停止 | 开始或停止连点循环,图标切换(播放/停止) | 点击时若无无障碍服务或尚无点击点会弹出提示 |
| 添加 | 在屏幕中央创建一个新的编号点击点 | 仅非运行状态显示 |
| 减少 | 删除最后添加的点击点(若为滑动对则整个删除) | 仅非运行状态显示 |
| 滑动 | 创建一对滑动起点(A)和终点(B),中间有管道连接 | 仅非运行状态显示 |
| 设置 | 打开全局设置对话框(循环次数、命名脚本管理) | 仅非运行状态显示 |
| 透明度 | 打开「透明度」弹框,分别调节悬浮窗与点击 / 滑动点位的透明度(20%~100%) | 仅非运行状态显示,与代点器各自独立保存 |
| 退出 | 关闭悬浮窗服务并通知主界面 | 仅非运行状态显示 |
运行时行为:
- 展开栏只保留主按钮与停止图标,其余按钮全部隐藏;主按钮仍可展开/折叠
- 点击停止按钮结束连点;点击悬浮窗上的空白区域也会立即停止(主按钮所在区域除外)
- 停止后完整按钮栏恢复,点击点重新可见、可拖动
- 运行中不再自动改变悬浮窗透明度,透明度完全由「透明度」弹框的设置决定
- 连点期间主按钮仍可拖动,松手后仍按当前方向吸附(竖排左 / 右;横屏或横排上 / 下,水平位置保留)
透明度:
- 「透明度」弹框内有两根滑块:「悬浮窗」(
root.alpha)与「点击 / 滑动点位」(所有点位标记与滑动通道视图的 alpha);滑块为统一的胶囊轨道 + 圆形滑钮样式(见 3.9AppSlider) - 按住滑块拖动时,弹框连同背后遮罩一起隐藏,只在当前这根滑块原来的位置叠一条 20% 不透明度的滑动进行条(轨道线、滑钮位置与滑块完全重合,显示当前百分比),滑钮始终在手指下方,方便直接观察真实透明度;松手后弹框恢复
- 「确定」持久化两个值,「取消」/返回键恢复打开前的值
- 连点器的两个值分别存于
clicker_script_config.xml的window_alpha与point_alpha,代点器只存window_alpha,两边互不影响,仅弹框页面相同 - 运行中点击 / 滑动点位保持你设置的透明度正常显示,不再压到 30%;手势穿透改由
FLAG_NOT_TOUCHABLE保证 - 运行中某个点位正在执行时,该点位(滑动时含起点、终点与通道)会短暂变为强调色(浅青 + 深色字),执行完恢复原样——用「变色」代替「消失」来提示这一步已执行
- 仅影响显示,不影响点位点击、拖动与手势穿透
创建:点击“添加”按钮,屏幕中央生成一个 44dp 的品牌青圆角方块标记(14dp 圆角、渐变底 + 白色描边 + 柔和投影),数字外再套一圈细白环(靶心),显示编号。
交互:
- 按住拖动:移动点击点到目标位置
- 长按(500ms):打开该点的设置对话框(全局悬浮窗样式)
- 短按:无响应(仅用于定位)
设置对话框内容:
- 按钮名称(只读,显示当前编号)
- 类型选项(单选):点击、双击、返回键、主页键、打开通知栏(滑动点仅显示“滑动”且不可修改)
- 点击位置(单选):中间 / 随机
- 延迟(毫秒)— 完成本点所有重复后等待的时间
- 触摸时长(毫秒)— 点击/滑动持续时间,滑动类型下标签显示为“滑动持续时间”
- 重复次数 — 当前点连续执行的次数
- 随机距离(像素)— 点击位置在中心点周围随机偏移的范围
- 取消 / 保存按钮
删除:
- 点击“减少”按钮删除最后添加的点击点
- 若为滑动对,同时删除配对点及管道
参数保存/加载:
- 所有点击点参数(坐标、类型、延迟、触摸时长、重复次数、随机距离等)可在「悬浮窗设置」中保存为命名脚本
- 点击脚本名即可加载并恢复全部点位、管道与参数;已有未保存点位时会先确认覆盖
创建:点击“滑动”按钮,屏幕中央生成一对 44dp 圆角方块标记(A1、B1,编号随 pair 递增)——起点为品牌青实心渐变(白字),终点为浅底描边态(浅青底 + 品牌色描边 + 深色字);两点之间绘制一条品牌青通道(12dp 半透明轨带 + 2dp 实线中线),靠近终点处带白色 V 形箭头(深色描边保证浅色背景下也清晰),通道跟随两点移动。
交互:
- 按住拖动任意一端:修改起点/终点坐标,管道实时拉伸/旋转,保持直线连接
- 长按 A1 或 B1:打开该滑动对的设置对话框(统一设置起终点参数)
- 起点类型为“滑动起点”,终点为“滑动终点”,设置页面类型固定为“滑动”且不可更改
管道特性:
- 直线连接两个圆心,始终可见
- 两端按标记半径留白,通道不会压在起点 / 终点标记上
- 管道无触摸响应,仅作视觉指示
- 拖动节点时管道自动更新
- 滑动执行期间,整条通道切到强调色(浅青),执行完恢复品牌青
删除:
- 点击“减少”删除滑动对时,同时移除起点、终点视图及管道
启动连点:
- 点击点与管道保持正常显示(沿用你设置的点位透明度),并添加
FLAG_NOT_TOUCHABLE使手势穿透 - 按点击点 ID 升序循环执行所有点
- 支持总循环次数限制(可在全局设置中配置,0 表示无限)
各类型动作实现:
| 类型 | 执行方式 |
|---|---|
| 点击 | 在目标坐标(含随机偏移)执行触摸,时长由触摸时长决定,重复“重复次数”次,完成后等待延迟 |
| 双击 | 两次快速点击(间隔约 100ms),重复执行指定次数;两次点击放在同一个 GestureDescription 的两条 stroke 里,分两次提交会被系统丢弃后一次 |
| 滑动 | 从起点中心坐标滑动到终点中心坐标,滑动持续时间由触摸时长控制,重复指定次数,完成后等待延迟 |
| 返回键 | 调用无障碍全局返回动作 |
| 主页键 | 调用无障碍全局主页动作 |
| 打开通知栏 | 调用无障碍全局打开通知栏动作 |
停止连点:
- 手动点击停止按钮、点击悬浮窗空白区域、通知栏“停止”按钮或应用被划掉均会停止
- 停止后恢复完整按钮栏,恢复点击点的可触摸状态并清除执行态高亮(透明度沿用你的设置)
执行链路的失败处理:
dispatchGesture提交失败或被系统取消(无障碍被关、被下层应用自己的手势打断)时,本轮先记下「手势被拒」,下一轮开始前停止整个循环并提示,不再对着已经看不见的界面空转- 每轮开始先确认无障碍实例还在,不在就停止并提示到系统设置重新开启
- 点击 / 滑动坐标(含随机偏移结果)统一夹进屏幕安全区(留 1dp 边距),边缘点位不会因越界导致手势失效
- 点位设置弹框保存时,延迟 / 触摸时长 / 重复次数 / 随机距离都按各自合法区间夹紧后再落盘,手打的超大值不会把节奏带坏
- 点位的编号(点击点与滑动对)按「当前最大号 + 1」分配,删除后不复用旧号:加载混合脚本时曾出现新点与残留点同号,会让执行顺序与保存顺序错乱
入口:点击悬浮窗“设置”按钮
内容:
- 总循环次数编辑框(0 或空表示无限循环)
- 「脚本」区:实心按钮「保存当前点位」+ 已保存脚本列表
- 保存:输入脚本名(最多 16 个字符,与代点器 / 录点器同一条规则),同名先确认覆盖
- 加载:点脚本名;当前有未保存点位时先确认覆盖,加载前自动停止连点
- 改名:输入新名称,新名已存在时提示
- 删除:红色确认框二次确认
- 列表为空时显示「暂无已保存脚本」空状态
- 取消 / 确定(仅保存循环次数)
行为:
- 对话框为全局悬浮窗样式,居中显示,背景透明,圆角白色卡片(与全项目弹框共用
AppDialogPanel统一外框) - 脚本列表与代点器的脚本管理共用
AppScriptList组件(行高 48dp、名称可点加载、右侧「改名 / 删除」) - 点击卡片外部区域可关闭(不保存循环次数)
页面结构(自上而下的分区式布局):
| 区域 | 内容 |
|---|---|
| Hero 卡片 | 应用图标 + 应用名 + 「当前模式 · 模式说明」,品牌色底承载身份信息 |
| 权限与状态 | 标题行:左侧「权限与状态」+ 就绪徽标(已就绪 / 待授权),行尾是紧凑胶囊按钮「刷新」(与徽标同尺寸、品牌底色),点击重新检测权限与悬浮窗服务状态;下方卡片三行状态(悬浮窗权限 / 无障碍服务 / 悬浮窗运行),每行由状态圆点 + 名称 + 状态徽标组成,行间细分隔线 |
| 获取权限 | 仅在权限未齐备时出现的实心按钮;权限齐备后由状态卡片说明一切,不再重复按钮 |
| 运行模式 | 标题行:左侧与「权限与状态」同款的分区标题,右侧为当前模块徽标(连点器 / 代点器 / 录点器);下方紧贴三选分段开关(间距 8dp,标题与控件成组),选择结果持久化。切换模块会自动关闭当前悬浮窗并启动目标模块;无障碍未开启时切换失败,保留原模块并提示 |
| 底部主操作 | 「启动 / 关闭悬浮窗」主按钮,平铺占满整行;未授权权限时禁用置灰,运行中为红色「关闭悬浮窗」 |
视觉规则:
- 页面底色用
background,卡片用surface+ 细描边(outlineVariant),靠亮度差而非阴影分层 - 状态用「圆点 + 徽标」表达,避免大块高饱和色块抢占注意力;正常态用中性徽标,成功态用成功色徽标
- 所有颜色 / 字号 / 圆角 / 间距均取自
core/theme的设计 token,页面禁止硬编码
全面屏适配:
MainActivity调用enableEdgeToEdge(),窗口铺满到状态栏 / 导航栏 / 刘海区域;页面底色继续铺满整屏,状态栏区域显示页面底色,与页面无缝- 页面内容按
WindowInsets.safeDrawing(状态栏 + 导航栏 + 刘海)加内边距避让,内边距加在滚动容器之外:内容既不会被状态栏遮盖,也不会滚进系统栏区域 - 系统栏图标颜色随系统深浅色自动切换(浅色用深色图标、深色用浅色图标)
广播监听:
- 当悬浮窗服务被关闭时,收到
SERVICE_STOPPED广播,自动更新界面按钮状态
主按钮有三种状态,启动时只有主按钮(折叠态),单击主按钮展开 / 折叠;尚无子按钮时单击会直接进入编辑态,方便先添加按钮或加载脚本:
| 状态 | 显示内容 |
|---|---|
| 折叠态 | 仅主按钮 |
| 展开态 | 主按钮 + 已配置的子按钮(纵向排列) |
| 编辑态 | 主按钮 + [+] + 子按钮列表 + [脚本管理] + [透明度] + [关闭] |
主按钮手势(与连点器一致,屏幕绝对坐标、单一触摸状态机):
- 单击:展开 / 折叠(处于编辑态时单击为退出编辑、保留展开)
- 双击:进入编辑态
- 按住拖动:移动悬浮窗,松手按当前方向吸附——竖排贴左 / 右;横排或横屏折叠态贴上 / 下,水平位置保留松手处,距边保留 12dp 且在安全区内
- 无障碍服务未连接时,主按钮变灰(半透明)并提示重新开启
子按钮:
- 名称限长:含中文最多 3 字、纯英文/数字最多 6 字符;名称放不下时按钮上的字自动缩小字号、不省略;留空时默认「按钮N」
- 非编辑态点击:先按「识别方式」拿到坐标(文本匹配 / 图像识别 / 坐标定位),再叠加随机偏移,最后按「动作类型」用
dispatchGesture执行点击 / 双击 / 长按 / 滑动(详见下方「识别方式」与「动作类型」) - 防抖:同一按钮在间隔内(默认 500ms)不重复执行。按按钮在列表里的位置计时而不是按名称计时——名称可以重复(默认名相同、改名后撞名),用名称会让两个不同按钮互相压制
- 编辑态点击子按钮:打开「编辑快捷按钮」弹框(与新建同表单,回填名称、目标文字或模板图与匹配参数、动作类型与偏移),保存后原地替换、顺序不变;弹框左下「删除」需二次确认后移除该按钮(图像按钮会一并清理其模板图文件)
- 编辑态长按拖动:长按后上下拖动子按钮可实时调整顺序(始终位于
[+]下方;长按原地松手不再触发编辑) - 非编辑态长按 / 滑动透传:运行态下子按钮仍接住触摸,但只有「点击」才执行配置的动作;按住超过系统长按阈值(约 500ms)按长按处理,位移超过
touchSlop按滑动处理,两者都回放给下层应用。系统不允许把进行中的触摸流转交给下层窗口,所以采用「识别后重放」:注入前先把悬浮窗临时置为FLAG_NOT_TOUCHABLE、等约 24ms 让标志生效,手势结束(或按「延时 + 时长 + 900ms」兜底超时)后再恢复;透传期间本窗口的触摸一律忽略,避免回放的手势点中主按钮。主按钮不参与透传(仍负责展开 / 折叠 / 拖动窗口)- 长按在达到阈值后立即注入一次 800ms 的按压,因此下层应用会在原按下位置后约 0.5s 触发长按;「长按后继续拖动」无法还原
- 滑动在抬手之后按采样轨迹(每 ≥8ms 一个点)整段回放,不是手指移动时实时跟手;多点触控 / 捏合不参与透传
识别方式:支持「文本匹配」「图像识别」「坐标定位」三种。
- 文本匹配:触发时在当前前台界面(
rootInActiveWindow)按三层顺序查找:① 系统findAccessibilityNodeInfosByText的结果做二次校验 → ② 自行遍历整棵节点树兜底(部分界面文字挂在深层节点,系统接口会漏)→ ③ 文字被拆成多个节点时(如「立即」+「抢购」)按「子树文字拼接」比对。文本比较统一去掉空白并忽略大小写,规避中文界面里插入的空格/换行;候选只保留可见且落在屏幕内的节点,优先可点击、其次文字完全相等、最后取最深的节点。全部落空时 Toast 提示「未找到目标」,并在 logcat 打出窗口包名与该界面的可见文字清单(adb logcat -s ClickService)。游戏、自绘 Canvas、部分 WebView 不向无障碍暴露文字,这类界面文本匹配必然失败,请改用图像识别或坐标点击。 - 图像识别:触发时由无障碍
takeScreenshot截屏(仅 Android 11+),用纯 Kotlin 归一化互相关(NCC)在截屏中滑窗匹配模板图,命中后取中心坐标执行所选动作;未找到可按参数重试。匹配采用「粗扫 + 逐级精扫」,且模板图解码与匹配都在工作线程执行(都在主线程会让点按钮时卡顿),结果回主线程点击;截屏前会隐藏悬浮窗与当前配置弹框(两者均置为不可触摸,弹框同时不可聚焦),避免它们进入截图或吞掉触摸。截图请求带 8s 兜底:个别机型takeScreenshot不回调也不报错,没有兜底会让「屏幕截图」入口永久点不动、悬浮窗保持隐藏回不来;截屏底图与裁剪结果都是显式回收,不留给 GC 决定时机。 - 坐标定位:新建/编辑弹框中手动填写屏幕像素坐标 X/Y;也可点「屏幕取点」——屏幕覆盖半透明置灰 + 网格帘(能看到下层应用),点选任意位置即自动回填坐标,触摸全部由拾取浮层消费,不会触发下层应用点击。执行时直接在配置坐标执行所选动作。
动作类型(识别拿到坐标之后如何执行;识别链路对四种动作完全一致):
| 动作 | 参数 | 手势构造 |
|---|---|---|
| 点击(默认) | 无 | 单点路径,按压时长固定 50ms(不用 0) |
| 双击 | 间隔 100~300ms(默认 150) | 同一个 GestureDescription 内放两条 stroke(起始时间 0 与 interval、坐标相同),一次提交,不连续提交两次 |
| 长按 | 时长 200~10000ms(默认 800) | 单点路径,按压时长即长按时长;输入超过 10s 按 10s 截断并 Toast 提示 |
| 滑动 | 方向 上/下/左/右、距离 50 |
moveTo 起点 → lineTo 终点(方向向量 × 距离)的直线 stroke;距离超过上限按上限截断并 Toast 提示。终点被夹在屏幕内后若与起点重合(零长度 stroke 系统直接拒收),先改朝反方向滑,仍不行就沿水平方向补 0.5px 保证手势有效 |
- 随机坐标偏移(所有动作共用):识别得到坐标后、构造手势前,X / Y 各加一个
[-N, +N]的随机整数;N 默认 5px、可调 0~50px,0 等同关闭,新建/编辑弹框内默认开启- 偏移只应用一次:双击的两次触摸共用同一个偏移后坐标;滑动只偏移起点,终点 = 偏移后的起点 + 方向向量 × 距离,不再二次随机
- 偏移结果与滑动终点最终都会夹到屏幕内、四周留 1dp 边距,避免
dispatchGesture因越界被系统拒绝
- 失败提示:
dispatchGesture提交失败(无障碍断连 / 被系统拒绝)时立即提示「手势提交失败,请到系统设置检查无障碍服务」并将主按钮置灰;手势被onCancelled取消时提示「手势被系统取消,请重试」,不会静默忽略
新建 / 编辑弹框的结构(与主页共用同一套 token 与组件):
- 「识别方式」用分段单选切换(文本 / 图像 / 坐标),与主页模块开关同款控件;下方一行灰字说明当前类型含义
- 识别相关配置装进带描边的卡片,与主页「分区 + 卡片」层次一致:文本 → 「目标文字」输入框;图像 → 「模板图片」(缩略图占位框 + 屏幕截图 / 从相册选 + 可折叠的高级匹配参数);坐标 → X / Y 输入框 + 实心「屏幕取点」按钮
- 「动作类型」用四段单选(点击 / 双击 / 长按 / 滑动,默认点击);只渲染所选动作的参数——点击无参数,双击 → 双击间隔,长按 → 长按时长,滑动 → 方向分段按钮 + 滑动距离 + 滑动时长;输入不合法时回落到默认值,超范围按上下限截断
- 参数区底部为随机坐标偏移分组:开关(默认开启)+ 偏移半径输入框(0~50px,默认 5,0 等同关闭)
- 内容超出弹框高度时由系统对话框滚动,滚到下方后可继续操作高级参数、勾选项与「新建 / 保存」按钮
截图 / 选图期间的显示与返回:
- 点「屏幕截图」或「从相册选」时,配置弹框与悬浮窗会先整体隐藏(均置为不可触摸、弹框不可聚焦),避免遮挡系统相册或被截进模板图;截图从「隐藏」一直保持到框选结束,确定、系统返回键取消、相册返回后自动恢复弹框与悬浮窗。
- 截图框选页与「屏幕取点」页不提供取消按钮,按系统返回键即退出该操作并回到「新建/编辑快捷按钮」弹框;相册选图页、相册裁剪页按返回键同样回到该弹框(不会关闭弹框)。
图像识别配置(新建/编辑弹框内,选「图像识别」后展开):
- 模板图来源:屏幕截图(截当前屏 → 全屏置灰+网格 → 框内保留原色、单指拖动、双指缩放选框 → 确定裁出区域)或 从相册选(系统 Photo Picker 选图后用同一选框 UI 裁剪);已选图显示缩略图,点击缩略图可重选;
- 裁剪比例:底部比例条支持 自由 / 1:1 / 4:3 / 3:4 / 16:9 / 9:16 / 自定义 W:H,默认「自由」。切换比例时以选框中心为锚点、保持面积不变地调整;锁定后单指拖动只移动选框,双指缩放只改变尺寸、比例保持不变,选框始终限制在图片范围内;
- 高级匹配参数:相似度阈值(默认 0.80)、降采样倍率(1~4,默认 2)、匹配区域(全屏 / 仅上次框选区域)、灰度匹配、未找到重试次数与间隔;
- 模板图存于应用私有目录
filesDir/images/,随脚本保存/加载,删除脚本或更换图片时清理旧文件。
各控件行为:
| 控件 | 行为 |
|---|---|
| [+] | 弹出「新建快捷按钮」表单:按钮名称、识别方式分段开关(文本 / 图像 / 坐标)、按类型展开的描边卡片配置(目标文字 / 模板图与匹配参数 / X·Y 坐标与屏幕取点)、动作类型四段开关与对应参数、随机坐标偏移(开关 + 半径) |
| 脚本管理 | 打开「脚本管理」:保存当前按钮为命名脚本、加载、重命名、删除 |
| 透明度 | 打开滑块弹框,拖动时悬浮窗实时变化;拖动期间弹框与遮罩隐藏,进行条(20% 不透明度)显示在被拖动滑块的原位置;「确定」保存并在下次启动时恢复,「取消」/返回键恢复原值 |
| 关闭(X) | 关闭悬浮窗服务;有未保存按钮时先询问「保存并关闭 / 不保存关闭 / 取消」,未命名列表会先要求输入脚本名 |
子按钮的修改与删除统一在「编辑快捷按钮」弹框内完成(编辑态点击子按钮打开),悬浮窗不再提供
[-]删除按钮。编辑弹框底部为「删除(红字)/ 取消 / 保存」三键,删除会再弹一次红色确认框。
各模块的脚本加载都会先做覆盖确认:代点器在「脚本管理」、连点器在「悬浮窗设置」、录点器在「文件」;连点器加载前会先停止连点。
连点器与代点器的全部弹框(新建 / 编辑按钮、脚本管理、透明度、关闭确认、全局设置、点击点设置、自定义裁剪比例)共用同一套弹框外框与按钮行,标题、分隔线、按钮位置完全一致。
与连点器、代点器完全独立的第三模块:只共用通用悬浮窗框架与弹框组件,脚本、存储、运行状态互不读写。
编辑态(展开面板):
单列,自上而下是 主按钮 + 信息条(有脚本时)+ 录制 + 启动 + 文件 + 设置 + 关闭,按钮与连点器 / 代点器同一形态(40dp 见方、竖排 4dp 间距、12dp 圆角、白色图标、4dp 轻投影)。主按钮固定在列首,展开 / 折叠只改变面板向下露出的长度,窗口原点不动——和代点器一致,不会出现整窗平移的抽帧感。
主按钮图标用 ic_main(三条横杠,与连点器 / 代点器主按钮同款),录制操作仍是 ic_record(同心圆),两者一眼可区分。
信息条只读、不可点,尺寸与主按钮完全一致(40dp 方块)。底色用品牌浅青(primaryContainer + onPrimaryContainer 文字)而非白色:白色方块夹在一排品牌实心按钮中间很突兀,浅青与按钮同色族、又靠「浅底深字」表明它是展示块而不是按钮(与主页模块徽标、准备录制条同一套语言)。没有任何脚本(0 步)时整个信息条不渲染,悬浮窗上不会出现「未命名脚本」之类的占位。编辑态只显示脚本名;运行态显示三行(脚本名 / 第 X/Y 轮 / 第 N/M 步),每行按可用宽度独立缩字号(9sp 起、每档 0.5sp 递减、下限 6sp),数字变长时三行可能出现不同字号,始终单行不换行。
脚本名限长与另外两个模块统一(core/util/ScriptNaming.MAX_NAME_LENGTH = 16 个字符),超出部分在输入时直接丢弃(不改写已有名称)。信息条只有 40dp 见方,名字长就靠自动缩字号,不再按中英文分别限长——旧的「中文 3 字 / 英文 6 字」规则比另外两个模块还严,同一个脚本名在不同模块能存的长度不一样。
横屏展开一律改用横排(主按钮在最左、随后是信息条与操作按钮,向右生长):横屏把 y 吸到上 / 下边,竖向面板只能沿吸附轴生长;横排的长边落在自由的 x 轴上,窗口原点同样不动。
| 控件 | 功能 |
|---|---|
| 主按钮 | 单击展开 / 折叠;按住拖动,松手按当前方向吸附(竖排左 / 右;横排与横屏折叠态上 / 下,水平位置保留) |
| 录制 | 常驻按钮;已有步骤或草稿时先确认「重新录制会清空当前步骤与草稿」 |
| 启动 | 先弹「开始回放」确认框(见运行态),确认后切到运行态并开始回放;0 步时置灰 |
| 文件 | 打开脚本管理:保存当前步骤 / 加载 / 改名 / 删除 / 恢复草稿 |
| 设置 | 回放倍速(0.5× / 1× / 1.5× / 2×)+ 悬浮窗透明度,弹框底部另有红色「关闭悬浮窗」 |
| 关闭 | 编辑操作的最后一项,ic_exit 关闭图标,与连点器 / 代点器的关闭按钮同款;走同一套关闭确认(回放中先停止、有未保存步骤时三选一) |
设置里的透明度滑块直接复用连点器 / 代点器那套内容组件:拖动时弹框与遮罩一起隐藏,只在这根滑块原来的位置留一条 20% 不透明度的百分比进行条,悬浮窗实时跟着变透明;松手弹框恢复,「确定」才落盘,「取消」/返回键恢复原值。
编辑态的关闭入口有两个:按钮列最后新增的「关闭」(与另外两个模块一致),以及「设置」弹框底部保留的红色「关闭悬浮窗」。两者走同一个 requestCloseWindow(),都会依次处理「回放中先停止」与「有未保存步骤时保存并关闭 / 不保存关闭 / 取消」。
准备 / 录制小条:
- 点「录制」后先出准备录制条:品牌浅底 + 引导文案「点开始后,除本栏、系统栏和安全界面外的触摸都会被记录」,右侧「开始 / 取消」;此时主窗隐藏,尚未开始记录。
- 小条底色固定 50% 半透明(标题、说明文字与按钮保持不透明,不跟随设置里的悬浮窗透明度),悬在应用上方时能看清下层内容。
- 只有≡ 把手(36dp 方块,按住拖动移动小条)与右侧两个按钮接收触摸;小条其余区域(标题、说明、内边距、底色)的点击直接穿透到底层应用。实现见
core/ui/WindowTouchRegion:用ViewTreeObserver#addOnComputeInternalInsetsListener的TOUCHABLE_INSETS_REGION把三段矩形(窗口坐标)设为唯一可点区域,该接口仍是 @hide,只能反射注册,被非 SDK 策略拦下时静默退回「整窗可点」。 - 点「开始」后创建全屏透明捕获层,小条切为录制态:白底红描边 + 呼吸红点 + 「录制中 · N 步」;每新增一步整条闪一下 +1、捕获层在落点画一个扩散圆环回显。录制态下捕获层在小条下面,小条的屏幕矩形会作为忽略区同步过去(拖动、旋转时刷新),落在小条非按钮区域的触摸既不记录也不提示,不会录出指向小条本身的幽灵步骤。
- 系统栏(状态栏 / 底部导航栏)区域的触摸只提示不记录;捕获层关闭时立即移除,不留残影。
- 录制中可「暂停」:捕获层变
NOT_TOUCHABLE(此时可正常操作 App),恢复触摸的其他应用;「继续」接回录制且不补算暂停期间的时间。 - 「停止」后弹出结束录制三选一:保存并结束(输入脚本名)/ 继续录制(回到录制中)/ 丢弃录制(红色二次确认);0 步时「丢弃录制」仍可用,但不再二次确认,直接结束本次录制回到编辑态,「保存并结束」保持置灰。
录制语义:
- 位移未超过
touchSlop且按压 < 长按阈值 →CLICK;按压达到长按阈值 →LONG_PRESS(记录实际按压时长);位移超过touchSlop→SWIPE(记录起终点与总时长)。 - 每步的
delayBefore= 上一步抬起 → 本步按下的间隔,是回放节奏的唯一来源;第一步固定为 0。 - 单次录制步数有上限(
RecorderScript.MAX_RECORD_STEPS),达到后自动暂停并提示。
录制流畅性(录制的操作手感由「捕获层 + 无障碍回显」决定,三者都已压到最小开销):
- 触摸必须由捕获层先消费、再通过无障碍手势回放给下层应用,因此每次操作都有一跳固定延迟;回显前的窗口标志切换等待从 48ms 降到 24ms(约一帧多),点按的跟手感明显好转。
- 草稿写入走单线程后台任务并做 250ms 合并:每落一步只更新内存快照,暂停 / 停止 / 服务销毁时立即补写,录制路径上不再有磁盘 I/O;删除草稿排在同一个线程末尾,不会被迟到的写入复活。
- 落点回显圆环只重绘它所在的矩形(约 64dp 见方),不再每帧整屏重绘;连续点击时先擦掉上一圈的残留,不会留下鬼影。
运行态(不显示任何编辑控件):
- 单列自上而下是 主按钮 + 信息条 + 启动/暂停 + 停止 + 返回,共四个按钮,同样只改变露出的长度、窗口原点不动。
- 信息条只读,三行依次是脚本名、
第 X/Y 轮、第 N/M 步;每行独立缩字号,三行可以不一样大。原来的「进度小块」与倍速标记并入 / 移除后删除(40dp 方块只容得下三行)。 - 轮数在启动前的「开始回放」确认框里定:标题「开始回放」,副标题「脚本名 · N 步」,中间是「重复次数」数字输入(1–999)与说明「范围 1–999;倍速 X×,只能在设置里改」,底部「取消 / 启动」。确认后写回当前脚本(已保存的脚本随即落盘),运行中不再提供修改入口。
- 暂停中图标变「继续」;停止按钮只在回放中可点,「停止」复位进度、停止注入并留在运行态(首按钮回到「启动」、停止置灰);「返回编辑」回到编辑态并恢复编辑控件,回放中会先二次确认。
- 每步先等待
delayBefore ÷ 倍速,再注入手势;2.0× 即等待减半,手势本身时长不变。倍速只能在「设置」弹框里改,运行态与启动确认框都只读展示。 - 每步注入前检查无障碍连接;注入被取消 / 提交失败时自动暂停并提示原因,不会静默跳过。
- 回放进度写入
session.json:切换模块或服务重启后恢复为暂停态并提示「点『继续』接着执行」,绝不自动注入。
草稿与切换模块:
- 录制每一步都落盘
draft.json;服务重启或切回录点器时若存在草稿,弹「发现未完成的录制」三选一:继续录制(进入准备录制条)/ 稍后处理(只载入步骤)/ 丢弃草稿(二次确认)。 - 录制中切换模块:先暂停并保留草稿,切回时由三选一提示接管,不自动恢复录制;回放中切换模块:立即停止注入并保留现场,切回时为暂停态。
- 无障碍未开启时无法切换模块(主页保留原模式并提示);切模块成功会提示「已切换到 X,悬浮窗已自动启动」。
- 保存脚本(无论从结束录制、脚本管理还是关闭弹框)时会同步清理同一脚本 id 的草稿,避免下次启动重复提示恢复。
脚本管理:列表显示脚本名与步数;「保存当前步骤」把当前步骤存为命名脚本(名称限长同代点器:中文 ≤3 字、英文 / 数字 ≤6 字符;同名先确认覆盖);加载前若当前有未保存步骤会先确认;删除当前脚本后回退到空编辑态(0 步,信息条不显示,「启动」置灰);存在草稿时提供「恢复草稿」入口。
Guard 规则:空脚本「启动」置灰(UI 第一层),引擎侧「脚本没有可回放的步骤」兜底;关闭 / 丢弃 / 删除等破坏性操作均二次确认。
页面与弹框共用一套 token 与通用组件,源码位于 core/theme 与 core/components。
配色(core/theme/Color.kt):
- 品牌主色为「钢青 Teal-Steel」:浅色
#0F6E7D,深色#5AD1E0;显式禁用 Material3 动态取色,保证跨设备观感一致 - 中性色与主色同 hue 家族(低饱和青灰),表面层级靠亮度差划分:
background<surfaceContainerLow<surfaceContainer<surfaceContainerHigh<surfaceContainerHighest - 卡片为
surface(浅色下即白色)+outlineVariant细描边;深色模式surface与background有明确亮度差,卡片不再糊成一片 success/warning/primaryPressed由AppSemanticColors承载(Material3 ColorScheme 无对应槽位),通过AppTheme.colors取用
排版(core/theme/Type.kt):
| 层级 | 字号 / 字重 | 用途 |
|---|---|---|
| headlineSmall | 24sp SemiBold | 页面主标题 |
| titleLarge / titleMedium / titleSmall | 20 / 17 / 15sp SemiBold | 区块标题、卡片标题 |
| bodyLarge / bodyMedium / bodySmall | 16 / 15 / 13sp | 正文、列表项、辅助说明 |
| labelLarge / labelMedium / labelSmall | 15 / 14 / 12sp | 按钮文字、表单字段标签、徽标 |
间距(core/theme/Dimens.kt):
- 基础阶梯 4 / 8 / 12 / 16 / 24 / 32
- 语义间距:
screenVMargin(页面上下留白)、sectionGap(分区之间)、blockGap(分区内块之间)、fieldGap(字段标签与输入框之间)、cardPaddingLg、dialogHPadding/dialogVPadding(弹框内边距) - 滑块尺寸:
sliderTrackHeight(8dp 胶囊轨道)、sliderThumbSize(24dp 圆形滑钮)、sliderThumbStroke(2.5dp 描边)、sliderThumbShadowIdle/sliderThumbShadowActive(3dp / 8dp 投影)
通用组件(core/components):
| 组件 | 说明 |
|---|---|
AppButton |
Filled / Outlined / Text / Danger / Success 五变体,含默认 / 按下 / 禁用 / 加载四态;Outlined 为 surface 底 + outlineVariant 描边 |
AppCard |
surface 底 + 可选细描边,不叠阴影;可点击时带 ripple |
AppText |
AppTitle / AppSectionTitle / AppBodyText / AppCaption / AppFieldLabel,全部走 MaterialTheme.typography |
AppTextField |
外观走 token;内核保留原生 EditText(overlay 弹框软键盘硬约束);回填文本经 InputFilter 变短时,选区按实际文本长度夹取,避免越界崩溃 |
AppSegmentedControl |
分段单选(模式切换、动作类型),轨道 surfaceContainer 底 + 细描边;圆角用 background(color, shape) 绘制而非 clip,保证整段矩形可点(圆角处不留死区) |
AppSelection |
AppRadioButtonRow / AppCheckboxRow,整行可点,触控高度 ≥ 48dp |
AppBadge |
状态徽标(Primary / Success / Warning / Neutral 四种色调),用于权限与运行状态;同文件另提供 AppPillButton——与徽标同尺寸的紧凑胶囊按钮 |
AppEmptyState |
列表无内容时的统一占位 |
AppScriptList |
命名脚本列表(连点器 / 代点器 / 录点器共用):脚本名点击加载,右侧「改名 / 删除」,空列表交给 AppEmptyState;内容超高时内部滚动 |
AppOpacityDialog |
透明度弹框内容(三个模块共用):说明文案 + 一或两根滑块 + 百分比;连点器额外传入 onPointAlphaChange 显示「点击 / 滑动点位」滑块,录点器把它嵌在设置弹框里与倍速同屏。拖动状态与滑块自身的屏幕矩形经 onDraggingChange 一并上报,由宿主隐藏弹框并把预览条放回这根滑块原来的位置 |
AppSlider |
统一滑块:胶囊轨道(未激活段 surfaceContainerHigh 底 + outlineVariant 细描边,激活段 primary)+ 圆形滑钮(surface 底 + primary 描边 + 投影,拖动时底色转 primaryContainer、投影加深);激活段右端与滑钮圆心严格对齐,交互沿用 Material3 Slider(点按跳转、拖动改值、无障碍语义),interactionSource 透传供调用方监听拖动 |
OpacityDragPreview |
拖动透明度滑块时的预览条(core/ui,原生 View 浮层):弹框窗口降到 alpha=0 并去掉遮罩,按滑块上报的屏幕矩形在原位置画一条 20% 不透明度的百分比进度条(轨道线、滑钮与 AppSlider 几何一致,填充比例同样按 20%~100% 映射),松手后恢复 |
WindowTouchRegion |
窗口触摸区域(core/ui):反射注册 ViewTreeObserver#addOnComputeInternalInsetsListener,用 TOUCHABLE_INSETS_REGION 把悬浮窗里只该响应的几块矩形(窗口坐标)设为唯一可点区域,其余位置点击落到下层窗口;该接口仍是 @hide,注册或写入失败时静默退回「整窗可点」,录制小条用它实现「只有 ≡ 把手与两个按钮能点」 |
SystemBarInsets |
全面屏适配工具(core/ui):liftBottomBarAboveNavigationBar() 把铺满整屏的页面 / 浮层的底部操作栏抬到导航栏之上,判据是操作栏底边是否触到屏幕底边;导航栏高度优先取窗口真实 systemBars inset,旧版本回退到系统资源 |
PointMarkerView |
连点器点位标记(feature/clicker,原生 View):44dp 圆角方块(14dp 圆角)+ 品牌青实心渐变 + 1.5dp 白色描边 + 柔和投影 + 顶部柔光;点击点为实心态且数字外套一圈细白环(靶心),滑动起点为实心态、滑动终点为浅底描边态(浅青底 + 品牌色描边 + 深色字);执行该点的瞬间切到强调色(浅青底 + 深色字 + 强调色投影),执行完复原 |
PipeLineView |
连点器滑动通道(feature/clicker,原生 View):品牌青半透明轨带(12dp)+ 品牌青实线中线(2dp)+ 末端白色 V 形箭头(深色垫底描边,浅色背景下也清晰);两端按标记半径留白,线条不压在标记上;滑动执行期间轨带与中线切到强调色 |
AppDialog |
全项目弹框的唯一外框:AppDialogPanel(标题区 → 1dp 分隔线 → 可滚动正文 → 1dp 分隔线 → 按钮区,圆角 20dp + surface 底,整体不超屏高 72%)、AppDialogActions(取消在左 / 主操作在右,可插 leading 弱化或危险操作)、AppConfirmMessage(确认框正文) |
统一弹框外观(除主页外,所有页面与弹框共用同一套外壳):
- 外框自绘圆角与底色,平台对话框面板底已剥离,不会出现「双层框」;标题、说明、正文、按钮的排版与间距由
AppDialogPanel统一给出 - 按钮行统一为「取消(白底描边)在左 / 主操作(品牌实心)在右」,危险操作主按钮换成红色实心,弱化操作(如「删除」)用红色文字
- 正文超高时在正文区内部滚动,标题与按钮始终可见(高度上限为屏高 72%)
悬浮窗与原生浮层的统一视觉:
- 连点器、代点器与录点器的悬浮窗按钮共用
FloatingSquareButton:40dp 见方、12dp 圆角、品牌实心底、白色图标、4dp 轻投影,按下加深;禁用态半透明且不画投影——半透明方块本身几乎看不见,只留一团投影压在相邻按钮上会像两个透明按钮叠在一起 - 截图框选页、相册裁剪页、坐标拾取浮层是原生 View,读不到 Compose 主题,故用
OverlayViews提供同款按钮(12dp 圆角、品牌实心或半透明描边)与底部操作栏底色(半透明黑 + 1dp 分隔线),色值统一取OverlayPalette - 全面屏适配:铺满整屏的原生浮层与页面(截图框选页、相册裁剪页)底部操作栏统一按导航栏高度上移,判据是「操作栏底边是否触到屏幕底边」——触到才抬,窗口本身已避让导航栏时不重复留白(
core/ui/SystemBarInsets.kt)
XML 侧同步规则:res/values/colors.xml 与 res/values-night/colors.xml 与 Color.kt 同源,改色必须两处同步且浅 / 深两份键对称;主题状态栏与页面背景同色(浅色用深色图标、深色用浅色图标)。
三个悬浮窗服务共用 core/ui/FloatingWindowBounds.kt,旋转屏幕或分屏尺寸变化后自动重排,并把所有窗口夹回可见区域:
- 坐标系校正:悬浮窗
LayoutParams.x/y的原点在内容区左上角(通常位于状态栏下方),并不是屏幕原点。usableSize()用getLocationOnScreen - x/y求出系统栏占位,再从getRealSize()中扣除;拖动、吸边与旋转钳制全部以「可用区域」为准,避免窗口底边 / 右边被状态栏、导航栏顶出屏幕。 - 安全区与吸边方向:
safeBounds()在主窗口四边再扣除状态栏、导航栏、挖孔与 12dp 内边距,得到真正可落点的安全区。吸附方向保证按钮条长边与吸附边缘平行:竖排贴左 / 右边缘;横排(含横屏下的折叠态,横屏时按钮条天然横向)贴上 / 下边缘。吸附只作用于长边的法线方向,另一轴保留用户松手时的位置(横条可以停在左上、正中、右下任意处,不会被迫靠右)。拖动过程中clampInside()只允许在安全区内移动;松手时按窗口中心落在半屏的位置决定贴哪一条边,旋转后沿用上次的吸附边。 - 展开沿生长方向外扩:主按钮固定在列首 / 行首,展开时面板向下(竖排)或向右(横排)生长,窗口原点不动,所以展开 / 折叠 / 切换运行态都不会出现整窗平移的抽帧感。录点器竖屏一律单列向下、横屏一律横排向右。
- 旋转重排:三个 Service 都实现
onConfigurationChanged(Service 不重建、运行状态保留),按屏幕宽高去重后重新选择布局并钳制主窗、点击点、滑动通道与录制小条;350ms 后再复检一次,覆盖旋转动画期间尺寸 / 坐标尚未稳定的情况。 - 横向展开栏:横屏(或分屏)可用高度装不下竖向按钮栏时,三个模块统一改为「主按钮 + 横向一排子按钮」;横排占用更充裕的屏幕宽度,横排也放不下时退回竖排并保持窗口在屏幕内。录点器更进一步:横屏展开一律横排,见上一条。
- 极端尺寸:两轴都放不下展开面板时,录点器 / 代点器提示「当前屏幕放不下展开面板,请旋转设备后再试」,不会静默裁切或把窗口留在屏幕外。
- 窗口大于屏幕:
clampOffset()在「屏幕尺寸 - 窗口尺寸」为负时固定为 0,避免coerceIn在负区间抛IllegalArgumentException。 - 判尺寸不看窗口位置:
screenUsableSize()直接用「真实屏幕 - 系统栏 - 内边距」,供布局方向判定使用。usableSize()靠getLocationOnScreen - x/y反推原点,旋转与弹框弹出的那一帧两者可能不同步,用误值判方向会锁死在横排。
连点器(命名脚本):
- 命名脚本以 JSON 数组存入 SharedPreferences(文件
clicker_script_config.xml,键scripts),每项含脚本名与点位列表;点位字段包括 ID、坐标、类型、点击位置、延迟、触摸时长、重复次数、随机距离、滑动对关联(pairId/isStart/ 终点坐标)等 - 加载脚本时先停止连点,再清除现有点位并按保存顺序重建视图、管道与配对关系
- 悬浮窗透明度存于同一 SharedPreferences 的
window_alpha键(0.2~1,默认 1),点击 / 滑动点位透明度存于point_alpha键(同范围),与代点器的同名键互相独立 - 旧版本的单份点位文件
/data/data/包名/files/click_points.json会在首次读取脚本列表时迁移为「默认脚本」,并写入legacy_migrated标记,只迁移一次(之后删空脚本也不会重新导入)
代点器(脚本):
- 命名脚本以 JSON 数组存入 SharedPreferences(文件
quick_click_config.xml,键scripts),每项含脚本名与其子按钮配置:- 公共字段:名称、类型(
text/image/coord)、目标文字、坐标 X/Y; - 动作字段:动作类型
actionType(click/double_click/long_press/swipe)、randomOffsetEnabled、randomOffsetRadius,以及按类型序列化的actionParams对象——双击interval、长按duration、滑动direction/distance/duration,点击为空对象; - 图像识别字段:模板图文件名、相似度阈值、降采样倍率、匹配区域(
full/last)、灰度匹配、重试次数与间隔、截图框选区域imageRegionRect("left,top,right,bottom",用于「仅上次框选区域」匹配); - 模板图本身的宽高比不单独存储,裁剪结果已经是按所选比例裁好的 PNG。
- 公共字段:名称、类型(
- 旧脚本兼容:读取脚本时若某条配置缺少
actionType,按「点击 / 偏移开启 / 5px」补默认值并回写一次;已有actionType的条目原样保留,迁移可重复执行且不会越迁越多 - 图像识别的模板图为 PNG,存于应用私有目录
filesDir/images/,脚本 JSON 中仅记录文件名;删除脚本、删除图像按钮或更换图片时会清理对应的旧图片文件 - 悬浮窗上正在编辑的按钮列表仅存在于内存,不会自动持久化;只有「保存当前按钮 / 另存为」命名后的脚本才会落盘,并可在「脚本管理」中再次加载
- 点击手势时长(默认 80ms)、防抖间隔(默认 500ms)与悬浮窗不透明度(键
window_alpha,范围 0.2~1,默认 1)同样存在该 SharedPreferences 中
录点器(录制脚本,独立存储):
- 已保存脚本以 JSON 数组存入 SharedPreferences(文件
recorder_config.xml,键scripts),每项带version(当前1)以及id/name/repeatCount/createdAt/updatedAt/screen(录制屏幕宽高与方向)/steps - 每一步记录
type(CLICK/LONG_PRESS/SWIPE,预留WAIT)与按类型取用的坐标、duration、delayBefore(上一步抬起 → 本步按下的间隔)。解析时逐字段校准到合法区间(坐标按录制屏幕宽高收敛、按压 50ms~10s、等待与间隔上限 10 分钟),无法修复的步骤跳过 - 回放倍速存同一 SharedPreferences 的
speed(0.5 / 1 / 1.5 / 2,默认 1),悬浮窗透明度存window_alpha(0.2~1,默认 1);三个模块的window_alpha互不影响 - 录制草稿存
filesDir/recorder/draft.json,每步只在内存里更新快照,落盘由后台单线程按 250ms 合并窗口写入(暂停 / 停止 / 服务销毁立即补写),服务被杀或切模块后可恢复;保存脚本时清理同一脚本 id 的草稿,「不保存关闭」「丢弃草稿」同样显式删除,避免下次启动重复提示 - 回放现场存
filesDir/recorder/session.json(当前脚本、进度、重复次数);切换模块或服务重启后恢复为暂停态,绝不自动注入 - 三个模块的脚本 JSON 都按「先解析、后覆盖写」处理:读取时逐条校验,出现无法解析的条目会拒绝用残缺列表覆盖全部脚本,并把原始值备份留存(
*_corrupt_backup),不会因为一条坏数据丢掉整份脚本 - 不参与系统云备份与设备迁移:
android:allowBackup="false"。脚本里存的是目标文字、屏幕坐标与模板截图,属于用户个人数据,不上传也不随设备转移;卸载即清除
- 兼容 Android 7.0(API 24)及以上
- 悬浮窗默认为竖排并吸附左侧边缘,可拖动;吸附方向跟随排布方向与屏幕方向(见 3.10)
- 设置对话框等采用
TYPE_APPLICATION_OVERLAY,确保在其他应用之上显示 - 前台服务运行时显示通知,提供停止按钮
- 三个悬浮窗服务(连点器 / 代点器 / 录点器)都用
foregroundServiceType="dataSync",并在 manifest 声明FOREGROUND_SERVICE_DATA_SYNC类型权限;Android 13+ 运行时申请POST_NOTIFICATIONS,被拒绝也不影响悬浮窗运行(仅通知栏入口不可见) - Android 15+ 的
dataSync时长上限由Service.onTimeout(startId, fgsType)接管:三个服务均主动stopSelf()优雅停止,避免系统以RemoteServiceException结束进程 - 主界面、悬浮窗与弹框使用 Jetpack Compose,经
ComposeView承载;Service 内用ServiceComposeHost补齐 Compose 所需的生命周期宿主,带输入框的TYPE_APPLICATION_OVERLAY弹框需清除FLAG_ALT_FOCUSABLE_IM才能弹出软键盘 - 三个悬浮窗服务的弹框统一由
ServiceDialogs.show()承载(平台AlertDialog+ 透明窗底 +TYPE_APPLICATION_OVERLAY),弹框内容统一套AppDialogPanel外框,三个悬浮窗的按钮统一用FloatingSquareButton ServiceDialogs会按服务登记每一个还开着的弹框,服务onDestroy时统一dismissAll()收掉:overlay 窗口不随 Service 销毁自动移除,漏收就会留在桌面上点不掉(录点器切模块时最容易碰到)- 主页的「启动 / 关闭悬浮窗」与模块切换有 600ms 连点保护:悬浮窗创建与销毁是异步的,连点会让
startForegroundService与stopService交叉,出现按钮说「已停止」但悬浮窗还在、或两个模块的悬浮窗叠在一起 - 调试日志统一走
core/util/DebugLog:只有安装包里带FLAG_DEBUGGABLE(debug 构建)才输出,release 不再把目标文字、窗口包名、屏幕坐标写进 logcat;错误与警告(Log.e/Log.w)不受门控,保证线上问题仍可查 - 无障碍截图(代点器图像识别)在专用单线程上做
HardwareBuffer→ARGB_8888转换,不再压在主线程;takeScreenshot在部分机型既不回调也不报错,因此带 3s 兜底超时并保证回调恰好一次,超时后释放已到的位图 - 代点器的截图框选页、裁剪比例条、坐标拾取浮层,以及录点器的全屏录制捕获层都使用原生 View 承载(全屏
TYPE_APPLICATION_OVERLAY场景下按键、触摸拦截与窗口焦点更可控);录制捕获层是叠在系统之上的透明窗口,暂停时置FLAG_NOT_TOUCHABLE把触摸交还下层应用 - 连点器在应用从最近任务划掉时自动停止服务并移除悬浮窗
- Android Studio:支持 Android Gradle Plugin 9.x 的较新版本
- Android SDK:编译 / 目标 SDK 36,最低 SDK 24
- Kotlin 2.2.x(由 AGP 9 内置,无需单独应用 Kotlin 插件)
- Android Gradle Plugin 9.1.0,Gradle 9.x
- Jetpack Compose(Compose BOM 2025.09.00),已在
app/build.gradle.kts开启buildFeatures.compose
- 本应用仅限个人使用,请勿用于违反服务条款或法律法规的行为
- 某些应用可能检测并阻止自动化点击操作
- 请合理使用,避免对设备性能造成影响
- 语言:Kotlin
- UI 框架:Jetpack Compose(Material3;悬浮窗与弹框使用
ComposeView承载) - 最低 SDK:API 24 (Android 7.0)
- 目标 SDK:API 36
- 编译 SDK:API 36
- 构建:Android Gradle Plugin 9.1.0(内置 Kotlin 2.2.x)