coordinates 自动化操作-坐标
coordinates 自动化操作-坐标
这一页专门讲 ScriptX 当前已经实现的坐标自动化接口。
先把结论摆前面:当前源码里还是 6 个坐标 API,但是它们已经不再只会打到“当前主屏”了,现在很多都能直接指定 displayId 或 vdisplay session:
click(x, y, displayId?)longClick(x, y, displayId?)press(x, y, duration, displayId?)swipe(x1, y1, x2, y2, duration, displayId?)Tap(x, y, displayId?)Swipe(x1, y1, x2, y2, duration?, displayId?)
像 Auto.js 文档里你可能见过的 gesture(...)、gestures(...)、setScreenMetrics(...)、RootAutomator 这一类,在当前这份 ScriptX 源码里没有找到对应脚本接口,所以这一页不会把它们写成“可以直接用”。
先记住这 12 条
- 小写的
click / longClick / press / swipe默认走无障碍手势。 - 大写的
Tap / Swipe默认走特权 shell / rootinput命令。 - 这一页的 6 个 API 现在都支持一个“虚拟屏目标参数”。
- 这个虚拟屏目标参数不只可以传
displayId数字,也可以传vdisplay.create()返回的 session 对象、session id 字符串,或者带target / screen / display / vdisplay / session / displayId的对象。 - 如果你之前已经
auto.setWindowFilter(screen)把自动化显示作用域锁到某个虚拟屏,很多坐标 API 在你省略 display 参数时,也会自动继承那块屏。 - 一旦小写的
click / longClick / press / swipe解析出了目标displayId,它们就不再走无障碍手势,而会切到 display input 路径。 - 这一页的所有坐标参数都要求是真正的数字;像
"100"这种数字字符串,在这里并不会自动帮你转。 - 小写 API 可以吃浮点数坐标;大写 API 最终会把坐标四舍五入成整数再执行命令。
- 所有时长参数都会被压到至少
1ms,不会出现0ms。 Swipe(...)的第五个参数可省略;省略时默认300ms。- 当前没有
setScreenMetrics(...)这类设计分辨率缩放层,所以你传的就是目标显示器自己的真实像素坐标。 - 这 6 个 API 的返回值全都是
boolean,不会把成功结果包装成复杂对象。
虚拟屏目标参数这一位到底怎么传
这是这次更新里最容易漏写、也是最容易把人搞糊涂的部分。
当前这 6 个接口都认的虚拟屏目标参数写法
| 写法 | 例子 | 是否推荐 | 说明 |
|---|---|---|---|
直接传 displayId 数字 | Tap(100, 200, 7) | 推荐 | 最直白 |
直接传 vdisplay session 对象 | click(100, 200, screen) | 最推荐 | 不用自己记数字 |
| 直接传 session id 字符串 | Swipe(100, 800, 100, 200, 300, "vdisplay-1") | 推荐 | 前提是这个 session 还存在 |
| 传 target 风格对象 | press(100, 200, 500, { screen: screen }) | 可用 | 当前源码会继续往对象里解析 |
| 省略目标参数 | Swipe(100, 800, 100, 200) | 推荐 | 当前会优先看有没有自动化显示作用域 |
另外,如果你手里本来就是一个 session 风格对象,顶层带着这些字段时,当前实现也能直接认:
__vdisplaySessionIdsessionIdiddisplayId
传错会怎样
如果你传的既不是数字、也不是能解析成目标虚拟屏的 session 对象、session id 字符串或 target 风格对象,当前源码会直接抛:
displayId must be a number or vdisplay session
这条报错文案是历史名字,别被它骗了。
虽然提示里只写了 “number or vdisplay session”,但当前实现实际上也接受:
"vdisplay-1"这种 session id 字符串{ screen: screen }/{ target: screen }这类对象写法
最稳的写法
const screen = vdisplay.create({ width: 720, height: 1280 });
Tap(100, 200, screen);
省略参数时会不会自动认到虚拟屏
会,但前提是你当前运行时已经有自动化显示作用域。
最典型的两种来源是:
- 你先调用过:
auto.setWindowFilter(screen);
- 你当前的自动化节点、截图、窗口根本来就都在那块虚拟屏上运行
这时你后面直接写:
Tap(100, 200);
Swipe(300, 900, 300, 300);
也可能已经打到了那块虚拟屏,而不是主屏。
小写和大写到底怎么选
先把最重要的区别讲清楚:
| API 组 | 默认依赖 | 典型优点 | 典型限制 |
|---|---|---|---|
click / longClick / press / swipe | 无障碍服务 | 不依赖 root;和 UiObject 系列心智一致 | 服务没起来时直接失败 |
Tap / Swipe | shell / root / 特权输入能力 | 不依赖无障碍节点树;某些场景更直接 | 没有特权执行通道时失败 |
但要补一句现在很关键的更新:
- 小写组只在没有目标 display 时才坚持走无障碍手势
- 一旦你给它们传了
displayId/vdisplay session,它们也会改走 display input 路线
一个很实用的经验是:
- 你已经在写无障碍自动化脚本,而且目标就是主屏,优先用小写组。
- 你要明确点某块虚拟屏,优先直接把目标参数传上去。
- 你根本不想依赖无障碍服务,只想打系统输入命令,就用大写组。
参数校验规则
这一页的参数校验和 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, displayId?)
这是默认走无障碍手势的单击。
click(540, 1200);
click(200, 300, screen);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 屏幕 X 坐标,必须是有限数字 |
y | number | 屏幕 Y 坐标,必须是有限数字 |
displayId | number | VDisplaySession | string | object | 可选;传了就把点击指到目标显示器 |
返回值
boolean
真实行为
不传 displayId
- 走无障碍点击手势
- 当前没有可用无障碍服务时,直接返回
false
传了 displayId / vdisplay session
- 不再走无障碍点击手势
- 会改成更接近
input -d <displayId> tap x y的显示器输入 - 这时它对目标虚拟屏的依赖更像
Tap(...)
示例:主屏普通点击
auto.waitFor();
click(540, 1200);
示例:点指定虚拟屏
const screen = vdisplay.create({ width: 720, height: 1280 });
click(100, 200, screen);
longClick(x, y, displayId?)
这是默认走无障碍长按的入口。
longClick(540, 1200);
longClick(180, 260, screen);
参数
和 click(x, y, displayId?) 一样。
返回值
boolean
典型场景
- 呼出长按菜单
- 触发拖动前的长按
- 某些只响应长按的控件
- 对某块虚拟屏的某个坐标做“长按一下”
一个重要细节
当你给 longClick(...) 传了 displayId / session 时,当前实现会走“同一点长时长 swipe”的 display input 路线,不再依赖无障碍节点动作。
示例
const node = textContains("聊天").findOne(1000);
if (node) {
const rect = node.bounds();
longClick(rect.centerX(), rect.centerY());
}
longClick(240, 420, screen);
press(x, y, duration, displayId?)
这是“带自定义时长的按压”。
press(540, 1200, 600);
press(240, 420, 900, screen);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 屏幕 X 坐标 |
y | number | 屏幕 Y 坐标 |
duration | number | 按压时长,单位毫秒,至少会被修正到 1 |
displayId | number | VDisplaySession | string | object | 可选;目标显示器 |
返回值
boolean
和 longClick 的区别
| API | 适合 |
|---|---|
longClick(x, y, displayId?) | 你只想表达“长按一下” |
press(x, y, duration, displayId?) | 你明确想控制按住多久 |
示例
press(540, 1200, 800);
press(240, 420, 1200, screen);
swipe(x1, y1, x2, y2, duration, displayId?)
这是默认走无障碍手势的滑动。
swipe(500, 1600, 500, 400, 300);
swipe(300, 1000, 300, 260, 280, screen);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
x1 | number | 起点 X |
y1 | number | 起点 Y |
x2 | number | 终点 X |
y2 | number | 终点 Y |
duration | number | 滑动时长,单位毫秒 |
displayId | number | VDisplaySession | string | object | 可选;目标显示器 |
返回值
boolean
真实行为
不传 displayId
- 依赖无障碍服务
- 没服务时返回
false - 时长会至少修正成
1ms
传了 displayId / session
- 会切到 display input 路线
- 更接近
input -d <displayId> swipe ...
示例
auto.waitFor();
swipe(500, 1600, 500, 400, 300);
swipe(300, 1000, 300, 260, 280, screen);
Tap(x, y, displayId?)
这是走特权输入命令的点击。
Tap(540, 1200);
Tap(120, 220, screen);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 屏幕 X 坐标 |
y | number | 屏幕 Y 坐标 |
displayId | number | VDisplaySession | string | object | 可选;目标显示器 |
返回值
boolean
和 click(x, y, displayId?) 的最大差别
click(x, y)默认是无障碍手势Tap(x, y)从一开始就是 display input / 特权输入思路
坐标处理细节
- 传入浮点数也能用
- 但最终会先四舍五入成整数坐标再拼命令
例如:
Tap(100.4, 200.6);
更接近执行:
input tap 100 201
示例
if (!Tap(540, 1200)) {
toast("Tap 失败,检查 shell / root 通道");
}
Tap(120, 220, screen);
Swipe(x1, y1, x2, y2, duration?, displayId?)
这是走特权输入命令的滑动。
Swipe(500, 1600, 500, 400);
Swipe(500, 1600, 500, 400, 450);
Swipe(300, 1000, 300, 260, 320, screen);
参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
x1 | number | 无 | 起点 X |
y1 | number | 无 | 起点 Y |
x2 | number | 无 | 终点 X |
y2 | number | 无 | 终点 Y |
duration | number | 300 | 滑动时长,单位毫秒 |
displayId | number | VDisplaySession | string | object | 当前自动化显示作用域里的 display 或无 | 虚拟屏目标参数,省略时优先继承当前自动化显示作用域 |
返回值
boolean
真实行为
- 最终执行的是类似
input swipe x1 y1 x2 y2 duration - 如果你传了目标显示器,就会走对应 display 的输入派发
- 坐标会先四舍五入成整数
duration省略时默认300- 传了小于等于
0的时长也不会报错,而是被修正成至少1
示例
Swipe(500, 1600, 500, 400);
Swipe(300, 1000, 300, 260, 320, screen);
常见组合示例
下面给你几段更像实战的写法。
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 screen = vdisplay.create({ width: 720, height: 1280 });
Swipe(
Math.floor(screen.width / 2),
Math.floor(screen.height * 0.82),
Math.floor(screen.width / 2),
Math.floor(screen.height * 0.22),
300,
screen
);
5. 先锁定作用域,再省略 display 参数
const screen = vdisplay.create();
auto.setWindowFilter(screen);
Tap(100, 200);
Swipe(200, 900, 200, 300, 320);
实战建议
优先用节点动作,坐标是补刀。
UiObject.click()、setText()这类能表达语义的 API,一般比裸坐标更稳。坐标一定尽量来源于节点边界,而不是手抄死值。
同一个按钮在不同分辨率、字体大小、刘海屏上位置都可能变。这一页的参数不认数字字符串。
click("540", "1200")这种写法在这里会报错,不像某些别的 API 会帮你转。没有
setScreenMetrics(...)。
所以如果你打算做跨设备脚本,自己按目标屏的宽高去算相对坐标会更靠谱。真正接了虚拟屏以后,不要再默认把“屏幕坐标”理解成主屏。
你传给Tap(...)、Swipe(...)的,是目标显示器自己的坐标系。
