何时用到视图操作
- 当第三方应用提供Api时,可以很方便调用api 来完成指令。例如地图App(高德/百度地图)调用Api方法:导航指令源码
- 当第三方应用无提供api时,可能需要脚本来模拟手势操作界面,并且目前VAssistant包含很多视图操作完成的指令。本文来详细介绍V-Assist 视图搜索/操作Api的使用方法。
视图搜索
我们看到的屏幕上内容,包括文字、图片和按钮等,这里统称为视图节点(ViewNode)。在操作界面时,需要先找到目标节点,查找需要指定节点特征,比如可以根据节点id、节点文字和视图节点描述(desc)来搜索。
视图操作三步法
指定搜索条件
===> 搜索
===得到结果===> 操作视图
- 普通点击:
text('目标文本').await().tryClick()
- 设置文本:
id('editview').findFirst().text = '123'
id
和text
来筛选视图;await
和findFirst
来进行搜索;tryClick
和text = '123'
进行视图操作。
另外,三步法中的搜索
可以省略,省略后的结果:text('目标文本').tryClick()
。在搜索条件后直接跟随操作代码,会默认等待搜索2s。
准备
- VAssistant 版本需要
1.9.8.4+
- 你可以使用
Visual Studio Code
配合vassist-debugger
插件来远程调试(推荐),或者使用App自带的脚本编辑器来调试脚本。
例子引入
以使用酷安更新全部应用
为例
模拟步骤:打开酷安
-> 点击主页右上角应用管理
-> 点击'全部更新'
脚本:
1
2
3
4
5
6
-- 打开酷安
system.openAppByPkg('com.coolapk.market')
-- 点击主页右上角应用管理
id('menu_badge_icon').tryClick()
-- 点击 全部更新
text('全部更新').tryClick()
可以看到脚本中使用了id('menu_badge_icon')
和text('全部更新')
来确定目标视图。
text
内容很好确定,那么id
从何而来?
这里提供两种方式获取视图节点信息。
1. 使用V-Assist-Debugge插件
打开酷安首页。
然后点击下图图标输出布局。
可以得到类似下文内容(为了篇幅,省略了部分内容):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|-0 {class: FrameLayout, bounds: Rect(0, 0 - 720, 1280), childCount: 2}
|-0 {class: LinearLayout, bounds: Rect(0, 0 - 720, 1280), childCount: 1}
|-0 {class: FrameLayout, bounds: Rect(0, 0 - 720, 1280), childCount: 1}
......
|-1 {class: FrameLayout, bounds: Rect(95, 42 - 556, 123), childCount: 1}
|-0 {class: FrameLayout, bounds: Rect(95, 42 - 556, 123), childCount: 1}
|-0 {class: LinearLayout, bounds: Rect(95, 57 - 556, 108), childCount: 2, Clickable}
|-0 {class: ImageView, bounds: Rect(109, 65 - 143, 99), childCount: 0}
|-1 {class: TextView, text: 万象息屏, bounds: Rect(157, 66 - 556, 98), childCount: 0}
|-2 {class: FrameLayout, bounds: Rect(556, 42 - 720, 123), childCount: 1}
|-0 {class: FrameLayout, bounds: Rect(556, 42 - 720, 123), childCount: 1}
|-0 {class: LinearLayout, bounds: Rect(570, 48 - 706, 116), childCount: 2}
|-0 {class: RelativeLayout, bounds: Rect(570, 48 - 638, 116), childCount: 3, Clickable}
|-0 {class: ImageView, id: menu_badge_icon, bounds: Rect(583, 61 - 624, 102), childCount: 0}
|-1 {class: View, bounds: Rect(583, 61 - 624, 102), childCount: 0}
|-2 {class: TextView, id: menu_badge, text: 1, bounds: Rect(600, 69 - 607, 86), childCount: 0}
|-1 {class: RelativeLayout, bounds: Rect(638, 48 - 706, 116), childCount: 2, Clickable}
|-0 {class: ImageView, id: menu_badge_icon, bounds: Rect(651, 61 - 692, 102), childCount: 0}
|-1 {class: View, bounds: Rect(651, 61 - 692, 102), childCount: 0}
|-1 {class: FrameLayout, bounds: Rect(0, 123 - 720, 186), childCount: 1}
|-0 {class: FrameLayout, bounds: Rect(0, 123 - 720, 186), childCount: 2}
........
|-0 {class: TextView, text: 上榜, bounds: Rect(0, 128 - 17, 165), childCount: 0}
|-3 {class: ActionBar$Tab, bounds: Rect(37, 123 - 192, 184), childCount: 1, Clickable}
|-0 {class: LinearLayout, id: linear_view, bounds: Rect(37, 128 - 192, 165), childCount: 1}
|-0 {class: TextView, text: #双十一#, bounds: Rect(57, 128 - 172, 165), childCount: 0}
|-4 {class: ActionBar$Tab, bounds: Rect(192, 123 - 286, 184), childCount: 1, Clickable}
|-0 {class: LinearLayout, id: linear_view, bounds: Rect(192, 128 - 286, 165), childCount: 1}
|-0 {class: TextView, text: 话题, bounds: Rect(212, 128 - 266, 165), childCount: 0}
|-5 {class: ActionBar$Tab, bounds: Rect(281, 123 - 385, 184), childCount: 1, Clickable}
|-0 {class: LinearLayout, id: linear_view, bounds: Rect(281, 126 - 385, 167), childCount: 1}
|-0 {class: TextView, text: 酷图, bounds: Rect(303, 126 - 363, 167), childCount: 0}
.........
{com.coolapk.market, com.coolapk.market.view.main.MainActivity}
主线程执行完毕
上面内容是布局层次关系,可以找到menu_badge_icon
的菜单元素。想找到目标节点并不容易,赶紧来看方法二。
2. 使用其他App分析布局
这里推荐AutoJs和开发者工具。下面以Auto.js
为例。
- 在Auto.js主页菜单开启
悬浮窗
- 点击悬浮按钮,出来的第三个按钮即为
布局分析
功能,选择屏幕上的节点即可查看对应信息。
视图匹配函数(第一步)
函数 | 说明 |
---|---|
id(id) | 指定视图id |
text(texts) | 文本匹配模式:相同文本,不区分大小写\n同equalsText |
equalsText(texts) | 文本匹配模式:相同文本,不区分大小写 |
similaryText(texts) | 根据文本相似度 > 0.75(中文转为拼音后的比较) |
containsText(texts) | 文本匹配模式:包含文本,不区分大小写 |
matchesText(regex) | 文本匹配模式:正则模式 |
desc(descs) | 匹配desc |
containsDesc(descs) | 包含desc |
type(types) | 匹配控件的className |
editable() | 匹配可编辑控件 |
scrollable() | 匹配可滑动 |
- 它们之间可链式调用来限制多个条件,如:
id('xxx').text('目标文本')
- 指定多个文本:lua:
text('aaa','bbbbb')
; js:text(['aaa','bbbbb'])
- 所有文本节点:
type('textview')
- 根据描述搜索:
desc('描述')
- 可滚动的视图:
scrollable()
搜索函数(第二步)
在确定限制条件后,即可开始搜索。搜索分为立即不等待和等待两种方法。
1
2
3
4
5
all_node = id('xxx').text('目标文本').find() -- 搜索并返回所有满足的视图节点
first = id('xxx').text('目标文本').findFirst() -- 搜索并返回第一个满足条件的节点
id('xxx').await() -- 等待指定节点出现(默认超时时间30s)
id('xxx').waitHide() -- 等待符合条件的视图消失,常用于等待加载视图消失(加载完成)
函数 | 说明 |
---|---|
findFirst() | 立即搜索,返回找到第一个,可能失败 |
find() | 搜索所有符合条件,返回Array |
waitFor() | 无限等待,直到搜索到,返回ViewNode |
waitFor(m) | 等待最长m毫秒,超时失败返回空 |
await() | 同waitFor() |
waitHide() | 等待消失 常用于加载View的消失,参数:([waitMs: Int])\n(可选)waitMs:等待时间,最长30s, 返回Boolean: false:超时; true:该ViewNode消失 |
视图操作(第三步)
在搜索到目标节点后可以进行的操作:
1
2
app_icon = id('menu_badge_icon').await() -- 等待出现
app_icon.tryClick() -- 点击
因为搜索可以省略,所以可以简写为id('menu_badge_icon').tryClick()
函数 | 说明 |
---|---|
tryClick() | 尝试点击” |
globalClick() | 使用全局函数click进行点击操作,如点击网页控件\n需要高级无障碍服务 |
swipe(dx, dy, delay) | 以此Node中心滑动到相对(dx,dy)的地方 |
tryLongClick() | 长按 |
longClick() | 长按 |
doubleClick() | 双击 |
setText(text) | 设置文本,一般只能用于可编辑控件 |
trySetText(text) | 设置文本 |
getChilds() | 获取下级所有Node,返回Array |
getParent() | 获取父级Node,返回ViewNode? |
getBounds() | 获取边界范围 |
getCenterPoint() | 获取中心点坐标Point(x,y)(相对于本机屏幕) |
getText() | 获取Node包含的文本 |
select() | 选择 |
trySelect() | 选择 |
focus() | 获得焦点 |
appendText() | 追加文本,适用于纯文本输入框 |
扩展
- 输入框模拟回车事件
在某些场景比如App内搜索,比如酷安搜索:输入框右侧有搜索图标,输入文字后可点击进行搜索;而网易云搜索框不存在搜索图标,很难执行类似输入法回车(类似Enter)的命令。
为此VAssistant
内置了输入法,并提供api来使用。
函数 | 说明 |
---|---|
init() | 设置VAssist内置输入法(无需显式调用) |
restore() | 恢复原输入法,指令结束后会自动恢复,也可显式调用,在合适时机恢复。 |
sendKey(keyCode) | 发送按键,keyCode 见系统函数sendKey说明 |
sendKeys(keys) | 发送一组按键 |
selectedText | 编辑框选择的文本 |
input(text)” to “输入 | ) |
sendDefaultEditorAction() | 发送默认输入框能响应的EditorAction事件 |
actionSearch() | 发送IME_ACTION_SEARCH |
actionGo() | 发送IME_ACTION_GO |
actionDone() | 发送IME_ACTION_DONE |
sendEnter() | 发送回车键 |
delete() | 删除键 |
select(start, end) | 选择文本 |
这里网易云搜索那一部分代码:
1
2
3
4
-- 进入搜索页
id("search_src_text").text = '歌曲名'
-- 无法获取弹框视图,使用发送按键
input.sendKey(66) -- 或 input.sendDefaultEditorAction()
- 等待进入App内
在界面跳转时需要等待进入后面页面再执行后面代码,可使用waitForApp(pkg, activityName)
例:等待进入酷安应用管理
1
2
3
4
5
6
7
8
-- 打开酷安
system.openAppByPkg('com.coolapk.market')
-- 点击主页右上角应用管理
id('menu_badge_icon').tryClick()
-- 等待进入应用管理
waitForApp('com.coolapk.market','AppManagerActivity')
-- 点击 全部更新
text('全部更新').tryClick()