//定义悬浮窗控制模块,命名为(悬块)。
var 悬块 = function(window, view) {
//判断是否缺少构造参数。
if (!window || !view) {
//缺少构造参数,抛出错误。
throw "缺参数";
};
//记录按键被按下时的触摸坐标
this.x = 0, this.y = 0;
//记录按键被按下时的悬浮窗位置
this.windowX, this.windowY;
//按下时长超过此值则执行长按等动作
this.downTime = 500;
//记录定时执行器的返回id
this.Timeout = 0;
//创建点击长按事件
this.Click = function() {};
this.LongClick = function() {};
//可修改点击长按事件
this.setClick = function(fun) {
//判断参数类型是否为函数?
if (typeof fun == "function") {
this.Click = fun;
};
};
this.setLongClick = function(fun, ji) {
//判断参数类型是否为函数?
if (typeof fun == "function") {
this.LongClick = fun;
//判断参数是否可为设置数字?
if (parseInt(ji) <= 1000) {
this.downTime = parseInt(ji);
};
};
};
view.setOnTouchListener(new android.view.View.OnTouchListener((view, event) => {
//判断当前触控事件,以便执行操作。
switch (event.getAction()) {
//按下事件。
case event.ACTION_DOWN:
//按下记录各种坐标数据。
this.x = event.getRawX();
this.y = event.getRawY();
this.windowX = window.getX();
this.windowY = window.getY();
//创建一个定时器用来定时执行长按操作。
this.Timeout = setTimeout(() => {
this.LongClick();
this.Timeout = 0;
}, this.downTime);
return true;
//移动事件。
case event.ACTION_MOVE:
//移动距离过大则判断为移动状态
if (Math.abs(event.getRawY() - this.y) > 5 && Math.abs(event.getRawX() - this.x) > 5) {
//移动状态清除定时器
if (this.Timeout) {
//定时器存在则清除定时器。
clearTimeout(this.Timeout);
this.Timeout = 0;
};
//移动手指时调整悬浮窗位置
window.setPosition(this.windowX + (event.getRawX() - this.x), this.windowY + (event.getRawY() - this.y));
};
return true;
//抬起事件。
case event.ACTION_UP:
if (this.Timeout) {
//手指抬起时,定时器存在,说明没有移动和按下时间小于长按时间。
//清除定时器。
clearTimeout(this.Timeout);
this.Timeout = 0;
//执行点击事件。
this.Click();
};
return true;
};
//控件的触控事件函数必须要返回true。否则报错。
return true;
}));
};
//创建并生成一个悬浮窗。
var window = floaty.window(
//创建一个按钮,并设置其id宽高等属性。
<button id="but" w="300px" h="150px" text="开始"/>
);
//空运行定时器保持脚本运行中,这是悬浮窗脚本所必需的。
setInterval(() => {}, 500);
//创建一个新的悬浮控制模块 ad 并带入参数(所要控制的悬浮窗和用来控制悬浮窗移动的控件)。
var ad = new 悬块(window, window.but);
//设置长按事件。
ad.setLongClick(function() {
//输出气泡信息。
toast("脚本已关闭");
//脚本停止代码。
exit();
});
//设置点击事件。
ad.setClick(function() {
//输出气泡信息。
toast("点击");
主程序()
});
为什么我点击运行整个程序直接闪退了
17:23:46.767/E: TypeError: 对象 [object Object] 不存在方法 setLongClick. (684854109a98757b5effd14d.js#65)
TypeError: 对象 [object Object] 不存在方法 setLongClick.
at 684854109a98757b5effd14d.js:65:0
可以使用了,但是点击悬浮按钮启动会直接闪退
回答
在你的代码中,导致闪退的主要原因是使用了 Rhino 不兼容的语法和错误的 OnTouchListener
创建方式。以下是具体的修正方法:
修正点 1:替换箭头函数为传统函数
Rhino 引擎不支持箭头函数,需将其替换为 function
形式,确保上下文正确。
修正点 2:正确创建 OnTouchListener
使用 android.view.View.OnTouchListener
时,需通过 new
和函数表达式创建,而非箭头函数。
修正点 3:保持 this
上下文
在事件处理中,this
可能指向错误的上下文,需通过闭包保留 self
。
修正后的代码如下:
//文档地址 https://docs.hamibot.com/
var 悬块 = function(window, view) {
if (!window || !view) {
throw "缺参数";
};
this.x = 0;
this.y = 0;
this.windowX = 0;
this.windowY = 0;
this.downTime = 500;
this.Timeout = 0;
this.Click = function() {};
this.LongClick = function() {};
var self = this; // 保留 this 上下文
view.setOnTouchListener(new android.view.View.OnTouchListener(function(view, event) {
switch (event.getAction()) {
case event.ACTION_DOWN:
self.x = event.getRawX();
self.y = event.getRawY();
self.windowX = window.getX();
self.windowY = window.getY();
self.Timeout = setTimeout(function() {
self.LongClick();
self.Timeout = 0;
}, self.downTime);
return true;
case event.ACTION_MOVE:
if (Math.abs(event.getRawY() - self.y) > 5 && Math.abs(event.getRawX() - self.x) > 5) {
if (self.Timeout) {
clearTimeout(self.Timeout);
self.Timeout = 0;
}
window.setPosition(self.windowX + (event.getRawX() - self.x), self.windowY + (event.getRawY() - self.y));
}
return true;
case event.ACTION_UP:
if (self.Timeout) {
clearTimeout(self.Timeout);
self.Timeout = 0;
self.Click();
}
return true;
}
return true;
}));
};
var window = floaty.window(
<frame>
<text id="but" bg="#FF0000" w="300px" h="150px" gravity="center">开始</text>
</frame>
);
setInterval(function() {}, 1000);
var ad = new 悬块(window, window.but);
ad.setLongClick(function() {
toast("脚本已关闭");
exit();
});
ad.setClick(function() {
toast("点击");
主程序();
});
function 主程序() {
// 这里添加你的主程序逻辑
toast("主程序运行");
}
关键改动说明
- 替换箭头函数:将所有箭头函数
() => {}
改为function() {}
。 - 保留
this
上下文:在悬块构造函数中用var self = this;
保持上下文。 - 使用
<frame>
包裹控件:确保 XML 布局结构正确,避免标签解析错误。 - 添加
主程序
函数:确保点击事件触发时有实际函数调用。
通过以上调整,脚本应能正常运行并响应点击与长按事件。
补充回答 1
你的错误是因为 ad
对象没有正确绑定 setLongClick
方法。问题出在 构造函数中对 this.LongClick
的定义方式 以及 事件处理函数的上下文丢失。以下是完整的修正方案:
✅ 核心问题分析
-
this.LongClick
未绑定到实例
this.LongClick = function() {}
会导致每个实例拥有独立的方法副本,但更严重的问题在于事件处理函数中的this.Timeout
和this.LongClick
上下文丢失。 -
OnTouchListener
中this
指向错误
事件处理函数内的this
不指向悬块
实例,而是指向 Android 的View
或其他上下文,导致无法访问this.Timeout
和this.LongClick
。 -
setLongClick
方法未正确绑定到实例
构造函数中通过this.setLongClick = function(fun, ji)
定义方法时,未使用prototype
,而事件处理函数中又无法访问this.setLongClick
。
✅ 修正后的完整代码
// 文档地址 https://docs.hamibot.com/
var 悬块 = function(window, view) {
if (!window || !view) {
throw "缺参数";
}
var self = this; // 保留 this 上下文
this.x = 0;
this.y = 0;
this.windowX = 0;
this.windowY = 0;
this.downTime = 500;
this.Timeout = 0;
this.Click = function() {};
this.LongClick = function() {};
// 定义 setClick 和 setLongClick 为实例方法
this.setClick = function(fun) {
if (typeof fun === "function") {
self.Click = fun;
}
};
this.setLongClick = function(fun, ji) {
if (typeof fun === "function") {
self.LongClick = fun;
if (parseInt(ji) <= 1000) {
self.downTime = parseInt(ji);
}
}
};
// 使用闭包绑定 this 上下文
view.setOnTouchListener(new android.view.View.OnTouchListener(function(view, event) {
switch (event.getAction()) {
case event.ACTION_DOWN:
self.x = event.getRawX();
self.y = event.getRawY();
self.windowX = window.getX();
self.windowY = window.getY();
self.Timeout = setTimeout(function() {
self.LongClick();
self.Timeout = 0;
}, self.downTime);
return true;
case event.ACTION_MOVE:
if (Math.abs(event.getRawY() - self.y) > 5 && Math.abs(event.getRawX() - self.x) > 5) {
if (self.Timeout) {
clearTimeout(self.Timeout);
self.Timeout = 0;
}
window.setPosition(self.windowX + (event.getRawX() - self.x), self.windowY + (event.getRawY() - self.y));
}
return true;
case event.ACTION_UP:
if (self.Timeout) {
clearTimeout(self.Timeout);
self.Timeout = 0;
self.Click();
}
return true;
}
return true;
}));
};
var window = floaty.window(
<frame>
<text id="but" bg="#FF0000" w="300px" h="150px" gravity="center">开始</text>
</frame>
);
setInterval(function() {}, 1000);
var ad = new 悬块(window, window.but);
// 确保方法正确绑定后再调用
if (ad && typeof ad.setLongClick === "function") {
ad.setLongClick(function() {
toast("脚本已关闭");
exit();
}, 1000); // 第二个参数可选,设置长按时间
} else {
toast("悬块初始化失败,请检查构造参数");
}
if (ad && typeof ad.setClick === "function") {
ad.setClick(function() {
toast("点击");
主程序();
});
} else {
toast("悬块初始化失败,请检查构造参数");
}
function 主程序() {
toast("主程序运行");
}
✅ 关键改动说明
改动点 | 说明 |
---|---|
self = this 替代 this |
防止事件处理函数中 this 丢失,确保上下文正确 |
setLongClick 绑定到 self |
保证在事件处理中可以访问到该方法 |
** |
补充回答 2
根据你的描述,点击悬浮按钮启动脚本时闪退,可能涉及以下原因:
✅ 关键问题分析
- 主程序逻辑异常
- 如果
主程序()
中包含未处理的异常(如未定义函数、空引用、非法操作等),会导致脚本崩溃。
- 如果
- 悬浮窗控件类型不兼容
- 若按钮使用
<text>
等非交互控件,可能导致点击事件绑定失败,间接触发逻辑错误。
- 若按钮使用
- 线程阻塞或资源占用
- 主程序中执行耗时操作未使用
threads.start()
,可能导致 UI 线程阻塞,触发 ANR(无响应错误)。
- 主程序中执行耗时操作未使用
- 对象释放或状态异常
- 悬浮窗或相关对象被提前关闭或释放,后续操作触发空指针异常。
✅ 解决方案
1. 确保主程序逻辑安全
- 在
主程序()
中添加异常捕获,防止崩溃。
function 主程序() {
try {
toast("主程序运行");
// 你的业务逻辑
} catch (e) {
toast("主程序异常: " + e);
}
}
2. 使用标准按钮控件
- 替换
<text>
为<button>
,确保点击事件正常触发:
var window = floaty.window(
<frame>
<button id="but" bg="#FF0000" w="300px" h="150px" text="开始" />
</frame>
);
3. 检查悬浮窗生命周期
- 确保在点击事件中未提前关闭悬浮窗(如调用
window.close()
)。 - 若需关闭悬浮窗,应在退出前移除所有事件监听器。
4. 异步执行耗时任务
- 主程序中涉及循环、网络请求、文件读写等操作时,必须放入子线程:
function 主程序() {
threads.start(function () {
// 耗时操作放在这里
sleep(1000); // 示例:模拟耗时操作
toast("操作完成");
});
}
5. 调试日志定位崩溃点
- 在关键位置添加日志,确定崩溃发生的位置:
ad.setClick(function() {
log("点击事件触发");
主程序();
});
✅ 修正后的完整代码示例
// 文档地址 https://docs.hamibot.com/
var 悬块 = function(window, view) {
var self = this;
this.x = 0;
this.y = 0;
this.windowX = 0;
this.windowY = 0;
this.downTime = 500;
this.Timeout = 0;
this.Click = function() {};
this.LongClick = function() {};
this.setClick = function(fun) {
if (typeof fun === "function") {
self.Click = fun;
}
};
this.setLongClick = function(fun, ji) {
if (typeof fun === "function") {
self.LongClick = fun;
if (parseInt(ji) <= 1000) {
self.downTime = parseInt(ji);
}
}
};
view.setOnTouchListener(new android.view.View.OnTouchListener(function(view, event) {
switch (event.getAction()) {
case event.ACTION_DOWN:
self.x = event.getRawX();
self.y = event.getRawY();
self.windowX = window.getX();
self.windowY = window.getY();
self.Timeout = setTimeout(function() {
self.LongClick();
self.Timeout = 0;
}, self.downTime);
return true;
case event.ACTION_MOVE:
if (Math.abs(event.getRawY() - self.y) > 5 && Math.abs(event.getRawX() - self.x) > 5) {
if (self.Timeout) {
clearTimeout(self.Timeout);
self.Timeout = 0;
}
window.setPosition(self.windowX + (event.getRawX() - self.x), self.windowY + (event.getRawY() - self.y));
}
return true;
case event.ACTION_UP:
if (self.Timeout) {
clearTimeout(self.Timeout);
self.Timeout = 0;
self.Click();
}
return true;
}
return true;
}));
};
var window = floaty.window(
<frame>
<button id="but" bg="#FF0000" w="300px" h="150px" text="开始" />