悬浮窗 - Floaty

# Floaty floaty模块提供了悬浮窗的相关函数,可以在屏幕上显示自定义悬浮窗,控制悬浮窗大小、位置等。 悬浮窗在脚本停止运行时会自动关闭,因此,要保持悬浮窗不被关闭,可以用一个空的setInterval来实现,例如: ``` setInterval(()=>{}, 1000); ``` ## floaty.window(layout) * `layout` {xml} | {View} 悬浮窗界面的XML或者View 指定悬浮窗的布局,创建并**显示**一个悬浮窗,返回一个`FloatyWindow`对象。 该悬浮窗自带关闭、调整大小、调整位置按键,可根据需要调用`setAdjustEnabled()`函数来显示或隐藏。 其中layout参数可以是xml布局或者一个View,更多信息参见ui模块的说明。 例子: ``` var w = floaty.window( <frame gravity="center"> <text id="text">悬浮文字</text> </frame> ); setTimeout(()=>{ w.close(); }, 2000); ``` 这段代码运行后将会在屏幕上显示悬浮文字,并在两秒后消失。 另外,因为脚本运行的线程不是UI线程,而所有对控件的修改操作需要在UI线程执行,此时需要用`ui.run`,例如: ``` ui.run(function(){ w.text.setText("文本"); }); ``` 有关返回的`FloatyWindow`对象的说明,参见下面的`FloatyWindow`章节。 ## floaty.rawWindow(layout) * `layout` {xml} | {View} 悬浮窗界面的XML或者View 指定悬浮窗的布局,创建并**显示**一个原始悬浮窗,返回一个`FloatyRawWindow`对象。 与`floaty.window()`函数不同的是,该悬浮窗不会增加任何额外设施(例如调整大小、位置按钮),您可以根据自己需要编写任何布局。 而且,该悬浮窗支持完全全屏,可以覆盖状态栏,因此可以做护眼模式之类的应用。 ``` var w = floaty.rawWindow( <frame gravity="center"> <text id="text">悬浮文字</text> </frame> ); w.setPosition(500, 500); setTimeout(()=>{ w.close(); }, 2000); ``` 这段代码运行后将会在屏幕上显示悬浮文字,并在两秒后消失。 有关返回的`FloatyRawWindow`对象的说明,参见下面的`FloatyRawWindow`章节。 ## floaty.closeAll() 关闭所有本脚本的悬浮窗。 ## floaty.checkPermission() * 返回 {boolean} 返回当前应用是否有悬浮窗权限。(不会触发请求权限操作) ## floaty.requestPermission() 跳转到系统的悬浮窗权限请求界面。 ```javascript if (!$floaty.checkPermission()) { // 没有悬浮窗权限,提示用户并跳转请求 toast("本脚本需要悬浮窗权限来显示悬浮窗,请在随后的界面中允许并重新运行本脚本。"); $floaty.requestPermission(); exit(); } else { console.log('已有悬浮窗权限'); } ``` 注意该函数并不会阻塞执行,也不会等待悬浮窗权限被授予。 # FloatyWindow 悬浮窗对象,可通过`FloatyWindow.{id}`获取悬浮窗界面上的元素。例如, 悬浮窗window上一个控件的id为aaa, 那么`window.aaa`即可获取到该控件,类似于ui。 ## window.setAdjustEnabled(enabled) * `enabled` {boolean} 是否启用悬浮窗调整(大小、位置) 如果enabled为true,则在悬浮窗左上角、右上角显示可供位置、大小调整的标示,就像控制台一样; 如果enabled为false,则隐藏上述标示。 ## window.setPosition(x, y) * `x` {number} x * `x` {number} y 设置悬浮窗位置。 ## window.getX() 返回悬浮窗位置的X坐标。 ## window.getY() 返回悬浮窗位置的Y坐标。 ## window.setSize(width, height) * `width` {number} 宽度 * `height` {number} 高度 设置悬浮窗宽高。 ## window.getWidth() 返回悬浮窗宽度。 ## window.getHeight() 返回悬浮窗高度。 ## window.close() 关闭悬浮窗。如果悬浮窗已经是关闭状态,则此函数将不执行任何操作。 被关闭后的悬浮窗不能再显示。 ## window.exitOnClose() 使悬浮窗被关闭时自动结束脚本运行。 # FloatyRawWindow 原始悬浮窗对象,可通过`window.{id}`获取悬浮窗界面上的元素。例如, 悬浮窗window上一个控件的id为aaa, 那么`window.aaa`即可获取到该控件,类似于ui。 ## window.setTouchable(touchable) * `touchable` {Boolean} 是否可触摸 设置悬浮窗是否可触摸,如果为true, 则悬浮窗将接收到触摸、点击等事件并且无法继续传递到悬浮窗下面;如果为false, 悬浮窗上的触摸、点击等事件将被直接传递到悬浮窗下面。处于安全考虑,被悬浮窗接收的触摸事情无法再继续传递到下层。 可以用此特性来制作护眼模式脚本。 ``` var w = floaty.rawWindow( <frame gravity="center" bg="#44ffcc00"/> ); w.setSize(-1, -1); w.setTouchable(false); setTimeout(()=>{ w.close(); }, 4000); ``` ## window.setPosition(x, y) * `x` {number} x * `x` {number} y 设置悬浮窗位置。 ## window.getX() 返回悬浮窗位置的X坐标。 ## window.getY() 返回悬浮窗位置的Y坐标。 ## window.setSize(width, height) * `width` {number} 宽度 * `height` {number} 高度 设置悬浮窗宽高。 特别地,如果设置为-1,则为占满全屏;设置为-2则为根据悬浮窗内容大小而定。例如: ``` var w = floaty.rawWindow( <frame gravity="center" bg="#77ff0000"> <text id="text">悬浮文字</text> </frame> ); w.setSize(-1, -1); setTimeout(()=>{ w.close(); }, 2000); ``` ## window.getWidth() 返回悬浮窗宽度。 ## window.getHeight() 返回悬浮窗高度。 ## window.close() 关闭悬浮窗。如果悬浮窗已经是关闭状态,则此函数将不执行任何操作。 被关闭后的悬浮窗不能再显示。 ## window.exitOnClose() 使悬浮窗被关闭时自动结束脚本运行。 # 示例 ## 悬浮文字 ```js var window = floaty.window( <frame gravity="center"> <text id="text" text="点击可调整位置" textSize="16sp"/> </frame> ); window.exitOnClose(); window.text.click(()=>{ window.setAdjustEnabled(!window.isAdjustEnabled()); }); setInterval(()=>{}, 1000); ``` ## 动态悬浮文字 ```js var window = floaty.window( <frame gravity="center"> <text id="text" textSize="16sp" textColor="#f44336"/> </frame> ); window.exitOnClose(); window.text.click(()=>{ window.setAdjustEnabled(!window.isAdjustEnabled()); }); setInterval(()=>{ //对控件的操作需要在UI线程中执行 ui.run(function(){ window.text.setText(dynamicText()); }); }, 1000); function dynamicText(){ var date = new Date(); var str = util.format("时间: %d:%d:%d\ ", date.getHours(), date.getMinutes(), date.getSeconds()); str += util.format("内存使用量: %d%%\ ", getMemoryUsage()); str += "当前活动: " + currentActivity() + "\ "; str += "当前包名: " + currentPackage(); return str; } //获取内存使用率 function getMemoryUsage(){ var usage = (100 * device.getAvailMem() / device.getTotalMem()); //保留一位小数 return Math.round(usage * 10) / 10; } ``` ## 护眼模式 ```js var w = floaty.rawWindow( <frame gravity="center" bg="#44ffcc00"/> ); w.setSize(-1, -1); w.setTouchable(false); setTimeout(()=>{ w.close(); }, 60000); ``` ## 悬浮窗输入框 ```js var window = floaty.window( <vertical> <input id="input" text="请输入你的名字" textSize="16sp" focusable="true"/> <button id="ok" text="确定"/> </vertical> ); window.exitOnClose(); toast("长按确定键可调整位置"); window.input.on("key", function(keyCode, event){ if(event.getAction() == event.ACTION_DOWN && keyCode == keys.back){ window.disableFocus(); event.consumed = true; } }); window.input.on("touch_down", ()=>{ window.requestFocus(); window.input.requestFocus(); }); window.ok.on("click", ()=>{ toast("傻瓜! " + window.input.text()); window.disableFocus(); }); window.ok.on("long_click", ()=>{ window.setAdjustEnabled(!window.isAdjustEnabled()); }); setInterval(()=>{}, 1000); ``` ## 悬浮窗运行脚本按钮简单版 ```js var path = "/sdcard/脚本/test.js"; if(!files.exists(path)){ toast("脚本文件不存在: " + path); exit(); } var window = floaty.window( <frame> <button id="action" text="开始运行" w="90" h="40" bg="#77ffffff"/> </frame> ); window.exitOnClose(); var execution = null; window.action.click(()=>{ if(window.action.getText() == '开始运行'){ execution = engines.execScriptFile(path); window.action.setText('停止运行'); }else{ if(execution){ execution.getEngine().forceStop(); } window.action.setText('开始运行'); } }); window.action.longClick(()=>{ window.setAdjustEnabled(!window.isAdjustEnabled()); return true; }); setInterval(()=>{}, 1000); ``` ## 悬浮运行脚本按钮 ```js var path = "/sdcard/脚本/test.js"; if(!files.exists(path)){ toast("脚本文件不存在: " + path); exit(); } var window = floaty.window( <frame> <button id="action" text="开始运行" w="90" h="40" bg="#77ffffff"/> </frame> ); setInterval(()=>{}, 1000); var execution = null; //记录按键被按下时的触摸坐标 var x = 0, y = 0; //记录按键被按下时的悬浮窗位置 var windowX, windowY; //记录按键被按下的时间以便判断长按等动作 var downTime; window.action.setOnTouchListener(function(view, event){ switch(event.getAction()){ case event.ACTION_DOWN: x = event.getRawX(); y = event.getRawY(); windowX = window.getX(); windowY = window.getY(); downTime = new Date().getTime(); return true; case event.ACTION_MOVE: //移动手指时调整悬浮窗位置 window.setPosition(windowX + (event.getRawX() - x), windowY + (event.getRawY() - y)); //如果按下的时间超过1.5秒判断为长按,退出脚本 if(new Date().getTime() - downTime > 1500){ exit(); } return true; case event.ACTION_UP: //手指弹起时如果偏移很小则判断为点击 if(Math.abs(event.getRawY() - y) < 5 && Math.abs(event.getRawX() - x) < 5){ onClick(); } return true; } return true; }); function onClick(){ if(window.action.getText() == '开始运行'){ execution = engines.execScriptFile(path); window.action.setText('停止运行'); }else{ if(execution){ execution.getEngine().forceStop(); } window.action.setText('开始运行'); } } ```