ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Autojs悬浮窗完全指南:floaty基础用法与可拖拽面板实战

Autojs悬浮窗完全指南:floaty基础用法与可拖拽面板实战 玩 Autojs 的朋友应该深有体会脚本写得再溜如果运行的时候没法在别的页面做点实时交互那体验就大打折扣。悬浮窗floaty就是解决这个问题的核心模块——它让你可以在任意应用上层显示一个自定义面板用来做按钮入口、状态提示、进度显示都顺手。我写 Autojs 脚本三年多从最早一个干巴巴的悬浮按钮到后来把整套自动化任务的操作面板都挂在悬浮窗里这中间的坑没少踩但这套东西确实值得每个玩 Autojs 的人掌握。这篇文章我会把 floaty 的基础用法、常用 API、一个可拖拽悬浮面板的完整实现以及权限、兼容、手感这些最容易踩雷的细节一次讲透。先说好这篇文章默认你已经装好了 Autojs我用的是较新版本的 Pro 版不过基础 API 大同小异会写一点简单的 JavaScript 语法。如果你连 Autojs 是什么都还不知道建议先跑几个录屏点击脚本再来不然上来就聊悬浮窗很多概念会接不住。[≈410字]1. 先搞清楚 floaty.window 和 floaty.rawWindow 的区别1.1 两种创建方式对应两种需求Autojs 的悬浮窗模块里官方提供了两个创建方法floaty.window()和floaty.rawWindow()。很多人第一次用就直接选floaty.window但实际场景里这两者的差异非常大选错了轻则耗电重则悬浮窗不显示或者挡住操作。floaty.window创建的是一个标准窗口它会参与系统窗口管理有自己的层级可以设置焦点、可以默认接受触摸事件而且它的生命周期和脚本本身并未完全绑定脚本停止后这个窗口如果没手动关掉很多时候还会“残留”在屏幕上。它的好处是稳定做可交互的悬浮按钮、菜单面板、悬浮键盘这类东西很合适。floaty.rawWindow则是一个“原始窗口”它不经过 Autojs 的窗口包装本质上是在根窗口上直接绘制内容。它的最大特点是轻量不抢占焦点初始状态下默认是不可触摸的需要手动设置setTouchable(true)适合做“只展示、不互动”的悬浮文本比如把脚本运行状态、剩余时间、倒计时这类信息挂在屏幕上。它的性能开销小不容易和当前应用抢焦点但你如果试图用它做一个精美的交互面板那就会出现触摸逻辑异常、点击穿透之类的奇葩问题。我自己的习惯是需要交互的悬浮按钮 / 菜单 →floaty.window只读状态展示 / 跑马灯文案 →floaty.rawWindow1.2 为什么选择悬浮窗而不是直接跳回 Autojs还有一个朴素的问题“我直接在 Autojs 主界面跑脚本不就行了为什么要用悬浮窗”原因很简单自动化脚本往往要操作第三方 App比如自动打卡、自动签到、自动回消息、自动抢券。这些操作需要你在目标 App 的界面里完成不可能来回切到 Autojs 控制脚本。悬浮窗就是一个“浮在目标界面上层的遥控器”它解决了脚本和人之间交互距离的问题。举一个最典型的场景你写了一个自动识别快递条形码的脚本识别过程中需要手动确认“屏幕上这个快递单号是否正确”这时候你总不能切出去扫描枪再切回来粘贴吧用悬浮窗放一个小面板上面显示识别结果和“确认”“重扫”两个按钮指头一点就完事。这种体验的提升是脚本逻辑本身给不了的。[≈800字]2. 环境准备与最基础的一个悬浮窗2.1 权限显示在其他应用上层这只是 Autojs 入门但也是新手的第一个大坑。悬浮窗要想显示到别的 App 上面必须要有“显示在其他应用上层”权限Android 6.0 及以上。Autojs 里查权限和请求权限的 API 在不同版本里名字不太一样老版本可能是floaty.checkPermission()新版本又变成了floaty.hasPermission()我建议直接用最通用的一段代码做保障// 检查悬浮窗权限 if (!floaty.hasPermission()) { toast(需要悬浮窗权限); floaty.requestPermission(); // 等待用户授权 wait(function() { return floaty.hasPermission(); }, 1000); }这段代码里wait()是 Autojs 的一个等待函数第一个参数是条件函数第二个是轮询间隔毫秒。注意不同版本返回结构可能略有差异如果floaty.hasPermission不存在就检查Settings.canDrawOverlays()或者换用floaty.checkPermission()。授权之后很多手机还需要在“最近任务”里给 Autojs 加锁防止 App 被系统清理掉因为悬浮窗本质上是由 Autojs 这个 App 进程托管的。脚本跑完了进程还在悬浮窗也在进程没了悬浮窗就没了。2.2 第一个可运行的悬浮窗代码我建议你新建一个脚本直接贴下面的代码跑一遍感受一下悬浮窗长什么样var window floaty.window( frame width200dp height120dp bg#66000000 text idstatus text悬浮窗 OK textColor#ffffff gravitycenter/ /frame ); window.setPosition(device.width / 2 - 100, 400); window.status.text(运行中); sleep(3000); window.close();这段代码里floaty.window()接收一个“视图描述”可以是 XML 布局、UI 布局字符串或者ui.inflate()出来的 View 对象。上面用的是 Autojs 里最常见的 XML 模板语法frame是帧布局text是文本控件idstatus给你一个后期操作这个控件的入口。之后window.setPosition(x, y)设置窗口左上角在屏幕上的绝对坐标。window.status.text(运行中)直接操作悬浮窗内部的status文本控件。window.close()关闭悬浮窗。跑完你应该能看到屏幕上出现一个半透明黑底白字的悬浮块3 秒后自动消失。如果看不到请回头检查权限如果看到了但文字被系统状态栏遮住那就是坐标问题后面细说。[≈900字]3. 悬浮窗常用 API 与核心细节3.1 位置、尺寸、可见性悬浮窗本质上是一个“窗口对象”所以它自然有窗口该有的操作。除了setPosition(x, y)还有setSize(width, height)接这两个方法可以随时改变悬浮窗的位置和尺寸。配合设备的屏幕宽高可以随意布局。如果需要微调位置可以用getX()和getY()获取当前坐标然后加上位移量。这是做拖拽功能的基础。还有个不太起眼的setAdjustEnabled(boolean)它允许悬浮窗自动避开系统状态栏和导航栏。如果你不想自己计算状态栏高度可以考虑开启它但实测部分机型开了之后会出现位置漂移所以我在做精确位置控制时更倾向于手动指定坐标把setAdjustEnabled(false)设死。3.2 触摸、点击与手势交互型悬浮窗最核心的是触摸事件。Autojs 的 View 对象支持click、longClick和touch事件。click写法最简单window.btn.click(function() { toast(点了悬浮按钮); });但如果你需要处理拖拽就必须挂setOnTouchListener自己解析ACTION_DOWN、ACTION_MOVE、ACTION_UP。下面这个例子是一个可以拖动的悬浮按钮var startX 0, startY 0, windowX 0, windowY 0; window.btn.setOnTouchListener(function(view, event) { switch (event.getAction()) { case event.ACTION_DOWN: startX event.getRawX(); startY event.getRawY(); windowX window.getX(); windowY window.getY(); return true; case event.ACTION_MOVE: // 用移动增量更新窗口位置 window.setPosition(windowX event.getRawX() - startX, windowY event.getRawY() - startY); return true; case event.ACTION_UP: return true; } return false; });这里有一点很容易踩坑event.getRawX()是手指在屏幕上的绝对坐标getX()是相对于被触摸 View 的坐标。如果误用getX()拖拽时窗口位置会一跳一跳永远跟不上手指。所以拖拽计算必须用getRawX()和getRawY()。另外ACTION_DOWN必须返回true意思是“我吃掉这个事件”后续的MOVE和UP才会继续分发给你。如果返回false事件就会走默认逻辑你的拖拽代码不会正常执行。3.3 控件查找与更新在floaty.window()创建的悬浮窗里可以通过 id 直接访问子控件。比如上面代码里window.status就是一个 Text 对象。如果你用了比较复杂的布局包含多个控件直接在返回对象上用点号接 id 就行。这一点类似 ui 选中的 selector但对象类型是原生 View。如果想动态控制其他属性通用的方法是拿到 View 后调用它的原生方法比如修改背景色、文字、可见性window.btn.getParent().setBackgroundColor(0xFFFF0000); window.menu.setVisibility(View.VISIBLE);需要注意的是Autojs 的 JS 环境里直接写View.VISIBLE通常是可以用的因为引擎把 Android 的 View 类暴露了。但如果你不确定可以直接用0表示GONE1表示VISIBLE4表示INVISIBLE。或者干脆用扩展方法window.menu.setVisible(true); // Autojs 的 Visible 方法我用下来setVisible更直观不容易搞混。3.4 rawWindow 的特殊性如果你用的是floaty.rawWindow必须记住它默认是不可触摸的。为了让文本展示区能响应点击你得先var rawWin floaty.rawWindow( text idinfo text状态栏 textSize14sp textColor#00ff00/ ); rawWin.setTouchable(true); rawWin.info.setClickable(true); rawWin.info.click(function() { toast(点击状态栏); });还有一点rawWindow不支持setSize之外的很多窗口属性不同版本支持度不一你最好在目标版本上多测一测。如果发现某些属性不生效换floaty.window基本都能解决。[≈1500字]4. 实战做一个可拖拽的悬浮控制面板4.1 功能设计悬浮窗最经典的实践是做一个“脚本遥控器”。我给自己的自动化打卡脚本做过这样一个面板悬浮一个圆形小按钮平时不遮挡操作单击小按钮展开一个包含“启动任务”“暂停任务”“关闭悬浮窗”的菜单按钮可拖拽松手后自动贴边脚本运行时面板显示当前状态比如“正在识别条形码”、“已暂停”脚本停止后悬浮窗自动关闭不残留。这个功能足够覆盖日常 80% 的需求。下面给一个经过修剪的可用版本。4.2 核心代码演示// 初始化悬浮窗权限 if (!floaty.hasPermission()) { floaty.requestPermission(); wait(function() { return floaty.hasPermission(); }, 1000); } // 创建悬浮窗 var window floaty.window( vertical bg#00000000 widthwrap_content heightwrap_content text idfab text◎ textSize24sp textColor#ffffff bg#ff6600 w48dp h48dp gravitycenter/ vertical idmenu bg#ccffffff w48dp visibilitygone text idstartTask text启动任务 textSize14sp padding6dp gravitycenter/ text idstopTask text暂停任务 textSize14sp padding6dp gravitycenter/ text idcloseWin text关闭 textSize14sp padding6dp gravitycenter/ /vertical /vertical ); // 初始位置屏幕右侧中间 var screenW device.width; var buttonW 48; window.setPosition(screenW - buttonW - 20, 400); window.setSize(48 * 2, 48 * 3); // 拖拽逻辑作用于悬浮球 var downX 0, downY 0, winX 0, winY 0; window.fab.setOnTouchListener(function(view, event) { switch (event.getAction()) { case event.ACTION_DOWN: downX event.getRawX(); downY event.getRawY(); winX window.getX(); winY window.getY(); return true; case event.ACTION_MOVE: window.setPosition(winX event.getRawX() - downX, winY event.getRawY() - downY); return true; case event.ACTION_UP: // 松手后贴到屏幕边缘 var currentX window.getX(); var currentY window.getY(); var newX currentX screenW / 2 ? screenW - buttonW - 20 : 20; window.setPosition(newX, currentY); return true; } return false; }); // 点击悬浮球展开/收起菜单 var menuIsOpen false; window.fab.click(function() { if (menuIsOpen) { window.menu.setVisibility(View.GONE); menuIsOpen false; } else { window.menu.setVisibility(View.VISIBLE); menuIsOpen true; } }); // 菜单按钮功能 window.startTask.click(function() { window.fab.setText(◉); // 改变悬浮球图标表示运行中 menuIsOpen false; window.menu.setVisibility(View.GONE); // 这里填你自己的自动化任务逻辑 startAutoTask(); }); window.stopTask.click(function() { window.fab.setText(◎); menuIsOpen false; window.menu.setVisibility(View.GONE); stopAutoTask(); }); window.closeWin.click(function() { window.close(); exit(); // 结束脚本线程 }); // 模拟任务函数 function startAutoTask() { // 示例循环打印日志 5 次 for (var i 0; i 5; i) { log(任务执行中: i); sleep(1000); } window.fab.setText(◎); } function stopAutoTask() { // 实际项目中这里要通过全局变量、线程控制等方式停止任务 toast(停止任务指令已发出); }需要注意上面的startAutoTask是同步函数会阻塞事件线程。实际中你多半会用threads.start()来跑后台任务避免悬浮窗无响应。后台任务里如果要更新悬浮窗 UI不能直接在子线程操作 View需要借助ui.run()var taskThread threads.start(function() { while (!isStoped) { // 模拟耗时操作 sleep(500); ui.run(function() { window.statusText.setText(处理中); }); } });4.3 布局与尺寸调整技巧我把悬浮按钮设计成 48dp是因为这是 Android 触控目标的最小建议尺寸太小了手指不好点。但悬浮窗的实际物理尺寸换算成像素时不同屏幕密度dpi下视觉大小会差很多。Autojs 的 XML 布局里写dp系统会自动转换所以尽量用 dp别裸写 px。菜单面板我放在悬浮球正下方因为整个悬浮窗是一个vertical上下排列正好。菜单默认visibilitygone这样初始状态下悬浮窗整体高度就是悬浮球的高度拖拽时的手感也干净。展开菜单后系统会自动重排。这里还有个细节菜单里的三个“按钮”我用的是text而不是button原因是button自带背景和边框有时候会和悬浮窗整体透明背景不搭。用text加padding模拟文本按钮视觉更轻量。如果你想要点击反馈可以在text上设置textColor或加背景也可以直接用button后自定义背景看你的审美。[≈1800字]5. 常见问题与排查实录悬浮窗这玩意写起来简单真到了手机上跑往往会遇到一堆奇怪的问题。我把这几年遇到过的坑集中列出来再附上排查思路。5.1 悬浮窗不显示优先级最高的是权限。不要只看 Autojs 里的权限状态还要去系统设置里确认“显示在其他应用上层”是否真的开启。很多国产 ROM 会额外问一次“是否允许自启动”或者“允许后台弹出界面”这些都影响悬浮窗能否显示。如果权限状态已经是允许但脚本跑完没看到窗口还有一个隐蔽原因Android 10 及以上把浮窗类型改了floaty.window如果声明了固定窗口类型某些系统版本下需要重新创建窗口类型为TYPE_APPLICATION_OVERLAY才能正常显示。Autojs 的现代版本基本都处理好了但如果你是从老版本升级上来的最好在 Android 10 上重新测试一下。另外有些场景下悬浮窗显示了但一切换到第三方 App 就自动消失。那多半是系统省电策略把 Autojs 进程拉到后台后冻结了去系统的电池优化设置里把 Autojs 设为“不优化”即可。5.2 悬浮窗显示但无法点击先检查是不是rawWindow没设置setTouchable(true)。这是最容易犯的低级错误。如果你用的是floaty.window点击没反应请检查悬浮窗是否被其他窗口盖住了。尤其是某些游戏 App自带高优先级悬浮层可能会挡住你的悬浮窗。这种场景你可以尝试调高窗口层级但 Autojs 的窗口类型受限不一定能盖过游戏浮层。更通用的方案是把悬浮球做成可以拖到边缘避开游戏的操作区域。还有一个奇怪问题某些国产 ROM 上floaty.window创建的窗口会“抢占焦点”导致悬浮窗的click事件不触发反而是点击穿透到下面应用。这时候可以考虑关闭窗口焦点window.setFocusable(false);不过关掉焦点后里面的输入框会没法打字具体取舍看场景。5.3 拖拽飘移、跳点拖拽跳点通常是坐标用错了。还记得前面强调的吗ACTION_MOVE里要计算event.getRawX() - downX作为位移增量位置基准是window.getX()。如果你写成了event.getX()这个值会相对于悬浮窗内部坐标拖拽就会乱跳。还有一种情况是悬浮窗内部有多个可拖拽的子控件每个子控件都设置了setOnTouchListener事件不一定能正确传到你的窗口坐标计算。我建议把拖拽事件统一挂在最外层容器或者悬浮球上菜单按钮不参与拖拽这样事件逻辑最简单。5.4 悬浮窗残留、关闭不了脚本停止后悬浮窗还在屏幕上是两个原因造成的一个是脚本进程没被彻底回收另一个是窗口引用还在。你在脚本末尾一定要调用window.close()。但如果你脚本里用了exit()有时反而会干掉整个进程导致窗口关闭的清理代码没执行完。我通常用两种方式来保证清理用一个全局变量保存窗口引用在需要停止时先window.close()再exit()。在main入口用try/finally包裹finally里做清理。不建议依赖系统自动回收。你永远不知道用户的手机会不会把进程冻结。5.5 状态栏高度与全屏适配悬浮窗定位时window.getX()返回的是窗口左上角在屏幕坐标系中的位置状态栏区域也计算在内。如果你把悬浮窗放到顶部(0, 0)它可能被状态栏遮住一部分。此时可以用statusBarHeight来偏移var statusBarHeight 0; if (device.sdkInt 21) { var res context.getResources(); var rid res.getIdentifier(status_bar_height, dimen, android); if (rid 0) { statusBarHeight res.getDimensionPixelSize(rid); } } window.setPosition(20, statusBarHeight 20);这段代码在大多数 Android 设备上都能拿到准确的状态栏高度。如果你的应用是沉浸式全屏也可以直接用activity.window.decorView什么的算但脚本环境下没有 Activity 上下文上面的方式更通用。5.6 性能与耗电悬浮窗的 UI 是实时渲染的如果你的悬浮窗上有大量文字、高频动画会明显增加系统渲染负担。特别是rawWindow里的文本每秒刷新很多次电池曲线会飙升。我的经验是文本更新用setText直接赋值不要反复重建 View。不用的时候让悬浮窗变透明或者setVisible(false)。涉及跑马灯动画的时候用系统自带的TextView属性实现别自己开线程定时改位置。实测下来一个静态悬浮球常驻后台耗电几乎可以忽略。只有频繁更新时才需要注意。[≈2000字]6. 悬浮窗在真实项目里的扩展思路写到底再聊点实战向的扩展。悬浮窗不只是“按钮 状态”这么简单它完全可以作为整个自动化脚本的“操作面板”和“信息看板”。比如你做快递条形码识别Android 端用 Autojs 挂载扫码模块识别过程中需要弹出一个悬浮窗实时显示识别到的条形码内容、单号、是否匹配。这个时候你会发现悬浮窗里放一个二维码/条码 ImageView 也没问题因为floaty.window()接受任意 View。再比如配合 Autojs 的插件机制悬浮窗可以成为插件入口点击悬浮球弹出菜单每一项对应不同插件任务比如“识别快递单号”、“自动填写表单”、“监控红包”等。你的整个自动化体系都可以收敛到这个小小的悬浮控件里。如果你的任务需要长时间运行我建议在悬浮窗的菜单里集成“锁定位置”“半透明”“缩小为点”三个功能。因为悬浮窗再小也存在遮挡做成可切换的“微小模式”能大幅降低对正常操作的影响。最后再说一句关于后台线程和悬浮窗的搭配Autojs 的脚本线程既可以threads.start()开启独立线程也可以直接写在主线程里。但悬浮窗事件回调运行在主线程UI 线程如果你在里面跑sleep(10000)整个悬浮窗就会冻结也不响应点击。所以我一般把耗时逻辑全部扔进独立线程需要刷新 UI 时用ui.run包一层。这也是悬浮窗项目里最容易出现“明明代码没错为什么点了没反应”的根本原因之一。从我个人的实际体验来说悬浮窗真的算得上 Autojs 里“一旦用上就回不去”的功能。它把脚本从“后台黑盒”变成了“看得见摸得着的工具”。你如果刚开始接触别贪多先跑透上面那个可拖拽按钮再往里加你自己的任务函数慢慢就会摸清这套机制的脾气。想把这部分玩得更顺建议多在不同品牌的真机上测一测悬浮窗这玩意很多问题是某个 ROM 特有的只有真机测试才能锁死解决方案。
返回列表