当前位置: 首页 > news >正文

Autojs基础-悬浮窗(floaty)

1.前言

这部分内容虽然后很多函数,但是我们几乎不会使用,这里就不一一介绍所有函数功能了。官方文档对于这部分介绍还是很详细的,我就不过多介绍了。我已经多次提及,悬浮窗的创建和销毁是有内存残余的,我们用悬浮窗来配置信息是没有问题的。但是,如果用于频繁创建和销毁的页面刷新过程中,很容易导致内存异常。

这部分内容,我就直接上案例了,如果了解代码以及使用方式,请好好查看代码中的注释。说实话,这个悬浮窗我并不是满意,只是能满足功能罢了。如果有时间,我真想重新做一个悬浮窗,配合自定义控制台控件,让悬浮窗兼容性更好。不过,如果简单使用下,这个悬浮窗还是足够了。

2.案例

除了xml文件放在ui文件夹中外,其他文件均在项目根节点,文件整体架构如下图:

project.json文件代码如下:

{ "name": "CSDN辅助", "main": "floaty.js", "launchConfig": { "hideLogs": false, "displaySplash": true }, "ignore": [ "build" ], "packageName": "com.py.csdn", "versionName": "1.0.0", "versionCode": 1 }

这个logo是我通过autojs应用分析获得的,可以根据个人喜好更换logo。

main.xml文件代码如下:

<vertical> <text text="配置页面"></text> </vertical>

js代码太多了,我是从项目摘取出来的。很难全部修改,我大体测试了下没有问题,如果有小bug请见谅。给出悬浮窗代码的目的还是供大家参考,floaty.js文件代码如下:

"ui"; // 获取当前应用包名 let currentAppPackageName = context.getPackageName(); // 是否运行中 let isRunning = false; // 是否进入游戏 let hasEnterGame = false; // 是否显示打印 let hasShowConsole = false; // 是否清理定时器等 let hasClear = false; // 是否打开悬浮窗菜单 let isMenuVisible = false; // 生产环境相对路径文件夹 let procRelativeFolder = "./"; // 项目名称,根据实际情况替换 let projectName = "悬浮窗"; // 开发环境相对路径文件夹 let devRelativeFolder = files.cwd() + "/" + projectName + "/"; // 相对路径文件夹 // 开发时,切换到开发环境 // 打包时,切换到生产环境 // 读取相对路径文件时,都采用这种方式 // 采用开发环境 let relativeFolder = devRelativeFolder; // ui保存文件夹 let uiFolder = relativeFolder + "ui/" // 保存文件总地址 let fileRootPath = "/storage/emulated/0/com.py.test/"; // 悬浮窗logo保存地址,由于免费版无法动态传值,需要在悬浮窗创建位置手动修改悬浮窗logo绝对路径地址。 let logoFilePath = fileRootPath + "logo.png"; // 如果图片不存在,复制一份过去 if (!files.exists(logoFilePath)) { // 读取图片 let img = images.read(relativeFolder + "logo.png"); //复制一份图片到总地址 images.save(img, logoFilePath, "png"); // 回收图片 img.recycle(); } // 需要按实际情况修改的变量 // 需要切换到横屏状态的应用包名 // 可以通过应用(app)部分介绍的内容,获取需要的包名 // 我这里就填写按键精灵的应用名了 let currentGamePackage = "com.cyjh.mobileanjian"; // 开启悬浮窗功能 let floatyThread = threads.start(function () { if (!checkFloatyPermission()) { toast("请开启悬浮窗权限"); // 跳转系统设置页(通用方法) app.startActivity({ action: "android.settings.action.MANAGE_OVERLAY_PERMISSION", data: "package:" + currentAppPackageName }); } }); // 悬浮窗功能开启结束后,执行下面代码 floatyThread.join(); // 检查悬浮窗权限 function checkFloatyPermission() { // 使用Android原生接口判断权限[4](@ref) return android.provider.Settings.canDrawOverlays(context); } // 获取当前时间戳 function getCurrentTime() { return new Date().getTime(); } // 停止逻辑线程 function stopStartLogicThread() { console.log("停止运行"); // 停止所有线程 threads.shutDownAll(); floatWindow.btnLeftStart.setText("启动"); floatWindow.btnRightStart.setText("启动"); } // 模拟启动逻辑线程 // 后期可以启动脚本逻辑代码模块 function startLogic() { toastLog("启动逻辑线程"); } // 切换回脚本页面 function switchScriptPage() { shell("am start -n " + currentAppPackageName + "/com.stardust.autojs.execution.ScriptExecuteActivity -f 0x20000000 --ez REORDER true", true); } // 启动主页面UI function startMainUi() { // 读取 XML 文件内容 ui.layoutFile(uiFolder + "main.xml"); } // 启动悬浮窗,必须在线程中启动,否则黑屏卡死 threads.start(function () { setFloaty(); }); // 强制停止脚本,并清理内存数据 function forceStopScriptAndCleanMemory() { floaty.closeAll(); let rt = java.lang.Runtime.getRuntime(); rt.gc(); } // 设置悬浮窗 function setFloaty() { // 工具函数:dp转px function dpToPx(dp) { return dp * context.getResources().getDisplayMetrics().density; } // 创建圆角按钮背景 function createRoundButtonDrawable(normalColorStr, pressedColorStr, radiusDp) { // 解析颜色 let normalColor = android.graphics.Color.parseColor(normalColorStr); let pressedColor = android.graphics.Color.parseColor(pressedColorStr); // 创建圆角图形 let normalDrawable = new android.graphics.drawable.GradientDrawable(); normalDrawable.setColor(normalColor); normalDrawable.setCornerRadius(dpToPx(radiusDp)); let pressedDrawable = new android.graphics.drawable.GradientDrawable(); pressedDrawable.setColor(pressedColor); pressedDrawable.setCornerRadius(dpToPx(radiusDp)); // 状态列表 let stateList = new android.graphics.drawable.StateListDrawable(); stateList.addState([android.R.attr.state_pressed], pressedDrawable); stateList.addState([], normalDrawable); return stateList; } let lastX = device.width / 2; let lastY = device.height / 2; // 需要修改img控件,src属性的logo绝对路径 floatWindow = floaty.window( <horizontal> <vertical id="menuLeft" visibility="gone"> <button id="btnLeftStart" text="启动" w="45" h="45" /> <button id="btnLeftConfig" text="配置" w="45" h="45" marginTop="8" /> <button id="btnLeftConsole" text="打印" w="45" h="45" marginTop="8" /> <button id="btnLeftExit" text="退出" w="45" h="45" marginTop="8" /> </vertical> <img id="mainImg" src="file:///storage/emulated/0/com.py.test/logo.png" w="40" h="40" /> <vertical id="menuRight" visibility="gone"> <button id="btnRightStart" text="启动" w="45" h="45" /> <button id="btnRightConfig" text="配置" w="45" h="45" marginTop="8" /> <button id="btnRightConsole" text="打印" w="45" h="45" marginTop="8" /> <button id="btnRightExit" text="退出" w="45" h="45" marginTop="8" /> </vertical> </horizontal> ); // 设置按钮样式 floatWindow.btnLeftStart.setBackground(createRoundButtonDrawable("#4CAF50", "#388E3C", 12)); floatWindow.btnLeftConfig.setBackground(createRoundButtonDrawable("#2196F3", "#1976D2", 12)); floatWindow.btnLeftConsole.setBackground(createRoundButtonDrawable("#4CAF50", "#388E3C", 12)); floatWindow.btnLeftExit.setBackground(createRoundButtonDrawable("#F44336", "#D32F2F", 12)); floatWindow.btnRightStart.setBackground(createRoundButtonDrawable("#4CAF50", "#388E3C", 12)); floatWindow.btnRightConfig.setBackground(createRoundButtonDrawable("#2196F3", "#1976D2", 12)); floatWindow.btnRightConsole.setBackground(createRoundButtonDrawable("#4CAF50", "#388E3C", 12)); floatWindow.btnRightExit.setBackground(createRoundButtonDrawable("#F44336", "#D32F2F", 12)); // 统一文字样式 [floatWindow.btnLeftStart, floatWindow.btnLeftConfig, floatWindow.btnLeftConsole, floatWindow.btnLeftExit, floatWindow.btnRightStart, floatWindow.btnRightConfig, floatWindow.btnRightConsole, floatWindow.btnRightExit].forEach(btn => { btn.setTextColor(colors.parseColor("#FFFFFFFF")); // 白色文字 btn.setTextSize(12); btn.setPadding(0, 8, 0, 8); btn.setElevation(4); // 添加投影 }); // 设备宽度和高度 let deviceWidth = device.width; let deviceHeight = device.height; // 初始化位置 let leftX = 10; let positionY = (deviceHeight - 120) / 2 + 250; let rightX = deviceWidth - 70; floatWindow.setPosition(rightX, positionY); floatWindow.setSize(120, 120); // 修改悬浮窗位置 function updateFloatWindowPosition() { // 如果进入游戏 if (hasEnterGame) { rightX = deviceHeight - 70; positionY = deviceWidth / 3 + 70; } else { rightX = deviceWidth - 70; positionY = (deviceHeight - 120) / 2 + 250; } floatWindow.setPosition(rightX, positionY); floatWindow.setSize(120, 120); } // 控制变量 let isDragging = false; let lastTouch = { x: 0, y: 0 }; let lastTime = undefined; let hasLastEnterGame = hasEnterGame; // 10秒未动,切换放到边上 let elfInterval = setInterval(() => { if (hasClear) { clearInterval(elfInterval); return; } // 判断是否进入游戏 hasEnterGame = currentPackage() == currentGamePackage; // 如果当前值和上一个值不一致 if (hasEnterGame != hasLastEnterGame) { // 关闭菜单栏 isMenuVisible = false; ui.run(() => { floatWindow.menuLeft.attr("visibility", "gone"); floatWindow.menuRight.attr("visibility", "gone"); }); // 修改悬浮窗位置 updateFloatWindowPosition(); hasLastEnterGame = hasEnterGame; } ui.run(() => { let currentX = floatWindow.getX(); let currentY = floatWindow.getY(); if (lastTime && (getCurrentTime() - lastTime >= 10000) && (currentX != leftX && currentX != rightX)) { isMenuVisible = false; floatWindow.menuLeft.attr("visibility", "gone"); floatWindow.menuRight.attr("visibility", "gone"); if (currentX <= device.width / 2) { floatWindow.setPosition(leftX, currentY); } else { floatWindow.setPosition(rightX, currentY); } floatWindow.setSize(120, 120); } }); }, 500); // 启动 floatWindow.btnLeftStart.click(function () { if (!isRunning) { floatWindow.menuLeft.attr("visibility", "gone"); floatWindow.setSize(120, 120); threads.start(function () { toast("启动成功!"); floatWindow.btnLeftStart.setText("停止"); floatWindow.btnRightStart.setText("停止"); floatWindow.setPosition(rightX, floatWindow.getY()); isMenuVisible = false; isRunning = true; startLogic(); }); } else { // 停止逻辑线程 stopStartLogicThread(); floatWindow.btnLeftStart.setText("启动"); floatWindow.btnRightStart.setText("启动"); isRunning = false; toast("脚本已停止!"); } }); floatWindow.btnRightStart.click(function () { if (!isRunning) { floatWindow.menuRight.attr("visibility", "gone"); floatWindow.setSize(120, 120); threads.start(function () { toast("启动成功!"); floatWindow.btnLeftStart.setText("停止"); floatWindow.btnRightStart.setText("停止"); floatWindow.setPosition(leftX, floatWindow.getY()); isMenuVisible = false; isRunning = true; startLogic(); }); } else { // 停止逻辑线程 stopStartLogicThread(); floatWindow.btnLeftStart.setText("启动"); floatWindow.btnRightStart.setText("启动"); isRunning = false; toast("脚本已停止!"); } }); // 配置 floatWindow.btnLeftConfig.click(function () { switchScriptPage(); ui.run(() => { floatWindow.menuLeft.attr("visibility", "gone"); floatWindow.setSize(120, 120); floatWindow.setPosition(rightX, floatWindow.getY()); // 启动主页面Ui startMainUi(); }); }); floatWindow.btnRightConfig.click(function () { switchScriptPage(); ui.run(() => { floatWindow.menuRight.attr("visibility", "gone"); floatWindow.setSize(120, 120); floatWindow.setPosition(leftX, floatWindow.getY()); // 启动主页面Ui startMainUi(); }); }); // 打印 floatWindow.btnLeftConsole.click(function () { ui.run(() => { floatWindow.menuLeft.attr("visibility", "gone"); floatWindow.setSize(120, 120); floatWindow.setPosition(rightX, floatWindow.getY()); }); if (!hasShowConsole) { hasShowConsole = true; threads.start(function () { // 显示控制台信息 console.show(); }); } else { hasShowConsole = false; // 控制台隐藏 console.hide(); } }); floatWindow.btnRightConsole.click(function () { ui.run(() => { floatWindow.menuRight.attr("visibility", "gone"); floatWindow.setSize(120, 120); floatWindow.setPosition(leftX, floatWindow.getY()); }); if (!hasShowConsole) { hasShowConsole = true; threads.start(function () { // 显示控制台信息 console.show(); }); } else { hasShowConsole = false; // 控制台隐藏 console.hide(); } }); // 退出 floatWindow.btnLeftExit.click(function () { // 强制停止脚本,并清理内存数据 forceStopScriptAndCleanMemory(); }); floatWindow.btnRightExit.click(function () { // 强制停止脚本,并清理内存数据 forceStopScriptAndCleanMemory(); }); // 增加监听器 floatWindow.mainImg.setOnTouchListener(function (view, event) { switch (event.getAction()) { // 按下 case event.ACTION_DOWN: lastTouch.x = event.getRawX(); lastTouch.y = event.getRawY(); isDragging = false; break; // 移动事件 case event.ACTION_MOVE: var dx = event.getRawX() - lastTouch.x; var dy = event.getRawY() - lastTouch.y; if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { isDragging = true; updatePosition(event.getRawX(), event.getRawY()); } break; // 点击事件 case event.ACTION_UP: if (!isDragging) { if (isMenuVisible) { // 隐藏菜单并缩小悬浮窗 if (floatWindow.menuLeft.attr("visibility") != "gone") { floatWindow.setPosition(floatWindow.getX() + 90, floatWindow.getY()); floatWindow.menuLeft.attr("visibility", "gone"); } else { floatWindow.menuRight.attr("visibility", "gone"); } floatWindow.setSize(120, 120); } else { // 根据位置显示不同的菜单按钮 let currentDeviceWidth = deviceWidth; if (hasEnterGame) { currentDeviceWidth = deviceHeight; } if (event.getRawX() <= currentDeviceWidth / 2) { floatWindow.menuRight.attr("visibility", "visible"); } else { // 如果距离太近,进行移动 if (currentDeviceWidth - floatWindow.getX() < 160) { floatWindow.setPosition(currentDeviceWidth - 160, floatWindow.getY()); } floatWindow.menuLeft.attr("visibility", "visible"); } floatWindow.setSize(220, 460); } isMenuVisible = !isMenuVisible; } break; } lastTime = getCurrentTime(); return true; }); // 位置更新方法 function updatePosition(x, y) { // 如果进入游戏 if (isMenuVisible) { let currentDeviceWidth = deviceWidth; // 如果进入了游戏 if (hasEnterGame) { currentDeviceWidth = deviceHeight; } lastX = Math.max(0, Math.min(x, currentDeviceWidth - 162)); } else { lastX = Math.max(0, Math.min(x, rightX)); } lastY = Math.max(0, Math.min(y, positionY)); floatWindow.setPosition(lastX, lastY); } }

3.总结

谢谢您读到这里!如果这篇文章对您有帮助且条件允许,欢迎通过打赏给予支持,当然,仅仅点赞我也同样感激,您的关注就是我持续分享的动力。如您打赏10元,可通过CSDN私信发给我QQ,我会邀请您加入交流群,群里会分享包括pyPlugin插件、项目源码等进阶资源,同时也可以咨询我关于AutoJS的问题——简单问题我会尽力解答,复杂问题则会在回复前后根据难度说明需要打赏的少量金额(2元、4元或6元),如未支付对应打赏,后续我将无法继续为您解答,还请理解。

需要说明的是,我无法回答那些涉及巨大工作量的问题,并且永远不会封装或回答涉及电话、短信等系统敏感操作的相关功能。如果您对AutoJS感兴趣,欢迎关注我,我会持续更新更多相关内容,再次感谢您的阅读与陪伴!

http://www.jsqmd.com/news/442451/

相关文章:

  • IP归属地数据赋能在线用户匹配:构建精准、高效的社交连接
  • AI专著撰写新玩法!揭秘高效工具,让专著写作不再是难题
  • 计算机毕业设计java基于JAVA的渝行旅游热点推荐系统 基于SpringBoot的重庆旅游智能推荐与攻略服务平台设计 渝行文旅信息整合与个性化推荐系统的研发
  • 用 OpenClaw + DeepSeek + Ollama 自动 Review Spring Boot 项目代码
  • LangChain工作流集成:LongCat-Image-Editn V2智能图像生成
  • 一篇文章带你搞懂“设计模式”! - - 观察者模式(17)
  • 从零开始开发 CNBlog MCP 工具
  • Z-BlogPHP版本历史 zblog网站常见问题1.7 Tenet修复
  • Qwen-Image-2512-Pixel-Art-LoRA效果展示:多主体交互场景像素化逻辑验证
  • 北京搬家公司服务哪家好?盘点5家口碑与实力并存的日式搬家品牌 - 资讯焦点
  • sudo权限添加
  • 2026 年证券纠纷律师专业实力排行榜 - 资讯焦点
  • 高价 + 安全 + 快速:卡券回收平台优选名单(2026 最新) - 资讯焦点
  • 【开题答辩全过程】以 基于SpringBoot私人牙医管理系统的设计与实现为例,包含答辩的问题和答案
  • Z-BlogPHP网站的c_option.php配置文件在哪里zblog网站常见问题
  • 高新能源渗透率园区源网荷储一体化规划及智慧能源管理系统 安科瑞 王文杰
  • 实战演练:用 CrewAI 搭建一套自动化的自媒体内容创作流水线
  • 【光纤通信】10 Gbps正交相位移键控QPSK光纤通信系统(真实的光纤损伤并分析系统在不同条件下的性能)【含Matlab源码 15103期】
  • VPS
  • 工商业储能升级新选择:Acrel-2000ESGXW 能量管理系统,赋能储能价值进阶 安科瑞 王文杰
  • SiameseUIE惊艳案例:含标点/数字/英文混排文本的稳定抽取
  • PHP抽象的核心概念的庖丁解牛
  • 【笔试真题】- 华子-2026.02.04-算法岗
  • 微信小程序开发实战:集成Lingbot-Depth-Pretrain-ViTL-14实现手机端深度测量
  • PHP多态的核心概念的庖丁解牛
  • 打造属于自己的物联网平台
  • 【笔试真题】- 华子-2026.03.04-算法岗
  • 联手攻克具身智能机器人固态电池,均胜电子与恩力动力达成战略合作
  • 闭眼入!10个AI论文网站测评:专科生毕业论文+开题报告高效写作指南
  • Harmonyos应用实例七:6-10的认识——数量感知与数序