让悬浮窗能够被拖动可以使用w.setAdjustEnabled(true);来实现。但是这样会给悬浮窗上面加三个大按钮,很不好看。有没有什么方法可以不使用这个w.setAdjustEnabled(true);来让悬浮窗可以被拖动?我看别人写的脚本可以不使用这个函数而且还能被拖动,而且可以按住窗体的任何位置来进行拖动,非常方便。
我查看了文档,events.observeTouch()这东西需要 root 权限,我没开启所i没法使用,因此我想不到可以实现此功能的方法了。
回答
要实现不依赖 setAdjustEnabled(true) 且无需 Root 权限的任意位置拖动悬浮窗,可以通过为悬浮窗的根布局绑定原生触摸监听器 setOnTouchListener,记录按下时的屏幕坐标与窗口坐标,在移动时计算偏移量并调用 setPosition() 更新位置。
以下是完整可运行的示例代码(已适配 Rhino 1.7.15 引擎,未使用箭头函数与默认参数):
// 文档地址 https://docs.hamibot.com/
var w = floaty.window(
<frame id="root" bg="#44ffcc00" w="200" h="100">
<text text="按住任意位置拖动" gravity="center" textColor="#000000" />
</frame>
);
var MotionEvent = android.view.MotionEvent;
var initialX = 0;
var initialY = 0;
var windowX = 0;
var windowY = 0;
w.root.setOnTouchListener(function(view, event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initialX = event.getRawX();
initialY = event.getRawY();
windowX = w.getX();
windowY = w.getY();
return true;
case MotionEvent.ACTION_MOVE:
var newX = windowX + (event.getRawX() - initialX);
var newY = windowY + (event.getRawY() - initialY);
w.setPosition(newX, newY);
return true;
case MotionEvent.ACTION_UP:
return true;
}
return false;
});
// 保持脚本运行,防止悬浮窗随脚本结束自动关闭
setInterval(function() {}, 1000);
核心原理说明
- 触摸事件拦截:
setOnTouchListener直接绑定在根布局root上,拦截整个窗口的触摸事件,无需 Root 权限。 - 坐标计算:
ACTION_DOWN时记录手指的绝对屏幕坐标getRawX()/getRawY()和当前窗口左上角坐标w.getX()/w.getY()。ACTION_MOVE时计算手指移动的差值,叠加到初始窗口坐标上,通过w.setPosition()实时更新位置。
- 事件消费:返回
true表示当前监听器已消费该触摸事件,悬浮窗会跟随手指移动,不会将事件传递给下层应用。
注意事项
- 脚本保活:悬浮窗的生命周期与脚本绑定,必须使用
setInterval或events.on("exit", ...)等机制保持脚本运行,否则窗口会立即销毁。 - 内部控件点击冲突:由于根布局消费了
ACTION_DOWN事件,窗口内的按钮或文本默认无法触发点击。如果窗口内需要保留可点击控件,建议将拖动监听器绑定到指定的“拖动条”或“标题栏”布局上,而不是整个根布局。 - 性能优化:
setPosition()调用频率较高,Rhino 环境下表现稳定。若拖动时出现轻微卡顿,可限制刷新频率(例如记录上次更新时间,间隔 16ms 再调用setPosition),但通常无需额外处理。 - 兼容性:代码严格遵循 Rhino 1.7.15 语法规范,未使用 ES6 特性,可在 Hamibot 环境中直接运行。
你可以直接复制上述代码到 Hamibot 脚本编辑器中运行测试。如需调整窗口大小、背景透明度或添加内部交互控件,只需修改 XML 布局部分即可。
ℹ️ 内容由 AI 生成,可能不准确,请注意核实