coordinates 自动化操作-坐标
coordinates 自动化操作-坐标
这一页专门讲 ScriptX 当前已经实现的坐标自动化接口。
先把结论摆前面:当前源码里只有 6 个坐标 API:
click(x, y)longClick(x, y)press(x, y, duration)swipe(x1, y1, x2, y2, duration)Tap(x, y)Swipe(x1, y1, x2, y2, duration?)
像 Auto.js 文档里你可能见过的 gesture(...)、gestures(...)、setScreenMetrics(...)、RootAutomator 这一类,在当前这份 ScriptX 源码里没有找到对应脚本接口,所以这一页不会把它们写成“可以直接用”。
先记住这 8 条
- 小写的
click / longClick / press / swipe走的是无障碍手势。 - 大写的
Tap / Swipe走的是特权 shell / rootinput命令。 - 这一页的所有坐标参数都要求是真正的数字;像
"100"这种数字字符串,在这里并不会自动帮你转。 - 小写 API 可以吃浮点数坐标;大写 API 最终会把坐标四舍五入成整数再执行命令。
- 所有时长参数都会被压到至少
1ms,不会出现0ms。 Swipe(...)的第五个参数可省略;省略时默认300ms。- 当前没有
setScreenMetrics(...)这类设计分辨率缩放层,所以你传的就是真实屏幕像素坐标。 - 这 6 个 API 的返回值全都是
boolean,不会把成功结果包装成复杂对象。
小写和大写到底怎么选
先把最重要的区别讲清楚:
| API 组 | 依赖 | 典型优点 | 典型限制 |
|---|---|---|---|
click / longClick / press / swipe | 无障碍服务 | 不依赖 root;和 UiObject 系列心智一致 | 服务没起来时直接失败 |
Tap / Swipe | shell / root / 特权输入能力 | 不依赖无障碍节点树;某些场景更直接 | 没有特权执行通道时失败 |
一个很实用的经验是:
- 你已经在写无障碍自动化脚本,优先用小写组。
- 你拿不到无障碍服务,或者明确想走
input tap/swipe,再考虑大写组。
参数校验规则
这一页的参数校验和 selector 那一组不一样,这里更严格。
坐标参数
x、y、x1、y1、x2、y2 的真实规则是:
| 传入值 | 结果 |
|---|---|
100 | 可用 |
100.5 | 可用 |
"100" | 抛异常 |
Infinity | 抛异常 |
NaN | 抛异常 |
null / undefined | 抛异常 |
也就是说,这一页的坐标参数只认 Number,不认“看起来像数字的字符串”。
时长参数
duration 的规则:
| 传入值 | 结果 |
|---|---|
300 | 可用 |
300.8 | 先转长整数,再执行 |
0 | 会被修正成 1 |
-20 | 会被修正成 1 |
"300" | 抛异常 |
null | 对必须参数会抛异常;对 Swipe(...) 的可选参数会走默认值 |
click(x, y)
这是无障碍单击。
click(540, 1200);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 屏幕 X 坐标,必须是有限数字 |
y | number | 屏幕 Y 坐标,必须是有限数字 |
返回值
boolean
真实行为
- 会调用当前活动无障碍服务的点击手势能力。
- 如果当前没有可用无障碍服务,直接返回
false。 - 不会帮你自动拉起无障碍设置页。
什么时候适合用
- 你已经通过
UiObject.bounds()算出了目标中心点 - 节点本身
click()老失败,但你还想继续走无障碍体系
示例
auto.waitFor();
click(540, 1200);
const node = text("登录").findOne(1000);
if (node) {
const rect = node.bounds();
click(rect.centerX(), rect.centerY());
}
longClick(x, y)
这是无障碍长按。
longClick(540, 1200);
参数
和 click(x, y) 一样,都是有限数字坐标。
返回值
boolean
典型场景
- 呼出长按菜单
- 触发拖动前的长按
- 某些只响应长按的控件
示例
const node = textContains("聊天").findOne(1000);
if (node) {
const rect = node.bounds();
longClick(rect.centerX(), rect.centerY());
}
press(x, y, duration)
这是“带自定义时长的按压”。
press(540, 1200, 600);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 屏幕 X 坐标 |
y | number | 屏幕 Y 坐标 |
duration | number | 按压时长,单位毫秒,至少会被修正到 1 |
返回值
boolean
和 longClick 的区别
| API | 适合 |
|---|---|
longClick(x, y) | 你只想表达“长按一下” |
press(x, y, duration) | 你明确想控制按住多久 |
示例
press(540, 1200, 800);
const root = auto.rootInActiveWindow;
if (root) {
const rect = root.bounds();
press(rect.centerX(), rect.bottom - 120, 500);
}
swipe(x1, y1, x2, y2, duration)
这是无障碍滑动。
swipe(500, 1600, 500, 400, 300);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
x1 | number | 起点 X |
y1 | number | 起点 Y |
x2 | number | 终点 X |
y2 | number | 终点 Y |
duration | number | 滑动时长,单位毫秒 |
返回值
boolean
真实行为
- 依赖无障碍服务
- 没服务时返回
false - 时长会至少修正成
1ms
示例
auto.waitFor();
swipe(500, 1600, 500, 400, 300);
// 从下往上拉,模拟上滑
swipe(
Math.floor(device.width / 2),
Math.floor(device.height * 0.8),
Math.floor(device.width / 2),
Math.floor(device.height * 0.2),
260
);
Tap(x, y)
这是走特权输入命令的点击。
Tap(540, 1200);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 屏幕 X 坐标 |
y | number | 屏幕 Y 坐标 |
返回值
boolean
和 click(x, y) 的最大差别
click(x, y):走无障碍手势Tap(x, y):最终执行的是类似input tap x y
坐标处理细节
这里和小写 click 不一样:
- 传入浮点数也能用
- 但最终会先四舍五入成整数坐标再拼命令
例如:
Tap(100.4, 200.6);
更接近执行:
input tap 100 201
失败时常见原因
- 当前 shell / root 通道不可用
- 宿主没有权限执行特权输入命令
- 输入命令本身执行失败
示例
if (!Tap(540, 1200)) {
toast("Tap 失败,检查 shell / root 通道");
}
Swipe(x1, y1, x2, y2, duration?)
这是走特权输入命令的滑动。
Swipe(500, 1600, 500, 400);
Swipe(500, 1600, 500, 400, 450);
参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
x1 | number | 无 | 起点 X |
y1 | number | 无 | 起点 Y |
x2 | number | 无 | 终点 X |
y2 | number | 无 | 终点 Y |
duration | number | 300 | 滑动时长,单位毫秒 |
返回值
boolean
真实行为
- 最终执行的是类似
input swipe x1 y1 x2 y2 duration - 坐标会先四舍五入成整数
duration省略时默认300- 传了小于等于
0的时长也不会报错,而是被修正成至少1
示例
Swipe(500, 1600, 500, 400);
Swipe(
Math.floor(device.width / 2),
Math.floor(device.height * 0.8),
Math.floor(device.width / 2),
Math.floor(device.height * 0.2),
350
);
常见组合示例
下面给你几段更像实战的写法。
1. 从 UiObject 跳到坐标点击
auto.waitFor();
const login = text("登录").findOne(1200);
if (!login) {
toast("没找到登录按钮");
exit();
}
const rect = login.bounds();
click(rect.centerX(), rect.centerY());
2. 某个节点动作不稳定时,改点中心
auto.waitFor();
const node = textContains("发送").findOne(1000);
if (!node) {
exit();
}
if (!node.click()) {
const rect = node.bounds();
click(rect.centerX(), rect.centerY());
}
3. 先试无障碍,失败再退到特权输入
function tapPoint(x, y) {
if (click(x, y)) {
return true;
}
return Tap(x, y);
}
tapPoint(540, 1200);
4. 上滑一屏
const x = Math.floor(device.width / 2);
const y1 = Math.floor(device.height * 0.78);
const y2 = Math.floor(device.height * 0.28);
swipe(x, y1, x, y2, 260);
5. 长按列表项 700ms
const item = textContains("群聊").findOne(1000);
if (item) {
const rect = item.bounds();
press(rect.centerX(), rect.centerY(), 700);
}
实战建议
优先用节点动作,坐标是补刀。
UiObject.click()、setText()这类能表达语义的 API,一般比裸坐标更稳。坐标一定尽量来源于节点边界,而不是手抄死值。
同一个按钮在不同分辨率、字体大小、刘海屏上位置都可能变。这一页的参数不认数字字符串。
click("540", "1200")这种写法在这里会报错,不像某些别的 API 会帮你转。没有
setScreenMetrics(...)。
所以如果你打算做跨设备脚本,自己按device.width / device.height去算相对坐标会更靠谱。
