Release scrcpy v1.16 · Genymobile/scrcpy (github.com)
- 保存js文件到手机
基于控件的操作
基于控件的操作指的是选择屏幕上的控件,获取其信息或对其进行操作。对于一般软件而言,基于控件的操作对不同机型有很好的兼容性;但是对于游戏而言,由于游戏界面并不是由控件构成,无法采用本章节的方法,也无法使用本章节的函数。有关游戏脚本的编写,请参考《基于坐标的操作》
基于控件的操作依赖于无障碍服务,因此最好在脚本开头使用
auto()
函数来确保无障碍服务已经启用。如果运行到某个需要权限的语句无障碍服务并没有启动,则会抛出异常并跳转到无障碍服务界面。这样对用户体验并不好,因为需要重新运行脚本,后续会加入等待无障碍服务启动并让脚本继续运行的函数您也可以在脚本开头使用
"auto"
表示这个脚本需要开启无障碍服务,但是不推荐这种做法,因为这个标记必须在脚本的最开头(前面不能有注释或其他语句、空格等),我们推荐使用auto()
函数来确保无障碍服务已启用
一.auto([mode])
mode
{string} 模式检查无障碍服务是否已经启用,如果没有启用则抛出异常并跳转到无障碍服务启用界面;同时设置无障碍模式为mode。mode的可选值为:
fast
快速模式。该模式下会启用控件缓存,从而选择器获取屏幕控件更快。对于需要快速的控件操作的脚本可以使用该模式,一般脚本则没有必要使用该函数。normal
正常模式,默认。如果不加mode参数,则为正常模式。
建议使用
auto.waitFor()
和auto.setMode()
代替该函数,因为auto()
函数如果无障碍服务未启动会停止脚本;而auto.waitFor()
则会在在无障碍服务启动后继续运行。
二.click(text[, i])
text
{string} 要点击的文本i
{number} 如果相同的文本在屏幕中出现多次,则i表示要点击第几个文本,i从0开始计算返回是否点击成功,当屏幕中并未包含该文本,或者该文本所在区域不能点击时返回false,否则返回true。该函数可以点击大部分包含文字的按钮,例如微信主界面下方的"微信","联系人","发现","我"的按钮等
通常与while同时使用以便点击按钮直至成功,例如:while(!click("扫一扫"));
当不指定参数 i 时则会尝试点击屏幕上出现的所有文字text并返回是否全部点击成功
i 是从0开始计算的,也就是,
click("啦啦啦",0)
表示点击屏幕上第一个"啦啦啦",click("啦啦啦", 1)
表示点击屏幕上第二个"啦啦啦"文本所在区域指的是,从文本处向其父视图寻找,直至发现一个可点击的部件为止
click(left, top, bottom, right)
left
{number} 要点击的长方形区域左边与屏幕左边的像素距离top
{number} 要点击的长方形区域上边与屏幕上边的像素距离bottom
{number} 要点击的长方形区域下边与屏幕下边的像素距离right
{number} 要点击的长方形区域右边与屏幕右边的像素距离注意,该函数一般只用于录制的脚本中使用,在自己写的代码中使用该函数一般不要使用该函数
点击在指定区域的控件。当屏幕中并未包含与该区域严格匹配的区域,或者该区域不能点击时返回false,否则返回true
有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标等),这时不能通过
click(text, i)
来点击,可以通过描述图标所在的区域来点击。left,bottom,top,right描述的就是点击的区域至于要定位点击的区域,可以在悬浮窗使用布局分析工具查看控件的bounds属性。通过无障碍服务录制脚本会生成该语句
三.longClick(text[, i]))
text
{string} 要长按的文本i
{number} 如果相同的文本在屏幕中出现多次,则 i 表示要长按第几个文本,i从0开始计算返回是否点击成功。当屏幕中并未包含该文本,或者该文本所在区域不能点击时返回false,否则返回true
当不指定参数i时则会尝试点击屏幕上出现的所有文字text并返回是否全部长按成功
四.scrollUp( [i] )
i
{number} 要滑动的控件序号找到第 i+1 个可滑动控件上滑或左滑。返回是否操作成功。屏幕上没有可滑动的控件时返回false
另外不加参数时
scrollUp()
会寻找面积最大的可滑动的控件上滑或左滑,例如微信消息列表等参数为一个整数 i 时会找到第 i+1 个可滑动控件滑动,例如
scrollUp(0)
为滑动第一个可滑动控件launchApp("微信") click("发现") click("朋友圈") scrollUp()
五.scrollDown([i])
i
{number} 要滑动的控件序号找到第 i+1 个可滑动控件下滑或右滑。返回是否操作成功。屏幕上没有可滑动的控件时返回false
另外不加参数时
scrollUp()
会寻找面积最大的可滑动的控件下滑或右滑参数为一个整数 i 时会找到第 i+1 个可滑动控件滑动。例如
scrollUp(0)
为滑动第一个可滑动控件
六.setText([i, ]text)
- i {number} 表示要输入的为第 i+1 个输入框
- text {string} 要输入的文本
返回是否输入成功,当找不到对应的文本框时返回false
不加参数 i 则会把所有输入框的文本都置为 text,例如
setText("测试")
这里的输入文本的意思是,把输入框的文本置为 text(会将之前的内容清除),而不是在原来的文本上追加
setText("你好!") sleep(2000) setText("你好呀!!!")
拿到获取焦点的文本框,将文本置为你所输入的文本
如果一个页面中有多个文本框,并且都没有获取焦点(也就是光标没有定位到任何一个输入框上),则会将所有文本框置为你所输入的文本
七.input([i, ]text)
- i {number} 表示要输入的为第 i+1 个输入框
- text {string} 要输入的文本
返回是否输入成功,当找不到对应的文本框时返回false
不加参数 i 则会把所有输入框的文本追加内容 text,例如
input("测试")
八.UiSelector
UiSelector即选择器,用于通过各种条件选取屏幕上的控件,再对这些控件进行点击、长按等动作。这里需要先简单介绍一下控件和界面的相关知识
一般软件的界面是由一个个控件构成的,例如图片部分是一个图片控件(ImageView),文字部分是一个文字控件(TextView);同时,通过各种布局来决定各个控件的位置,例如,线性布局(LinearLayout)里面的控件都是按水平或垂直一次叠放的,列表布局(AbsListView)则是以列表的形式显示控件
控件有各种属性,包括文本(text),描述(desc),类名(className),id等等。我们通常用一个控件的属性来找到这个控件,例如,想要点击QQ聊天窗口的"发送"按钮,我们就可以通过他的文本属性为"发送"来找到这个控件并点击它,具体代码为:
var sendButton = text("发送").findOne(); sendButton.click();
在这个例子中,
text("发送")
表示一个条件(文本属性为"发送"),findOne()
表示基于这个条件找到一个符合条件的控件,从而我们可以得到发送按钮sendButton,再执行sendButton.click()
即可点击"发送"按钮用文本属性来定位按钮控件、文本控件通常十分有效。但是,如果一个控件是图片控件,比如Auto.js主界面右上角的搜索图标,它没有文本属性,这时需要其他属性来定位它。我们如何查看它有什么属性呢?首先打开悬浮窗和无障碍服务,点击蓝色的图标(布局分析), 可以看到以下界面:
之后我们点击搜索图标,可以看到它有以下属性:
我们注意到这个图标的desc(描述)属性为"搜索",那么我们就可以通过desc属性来定位这个控件,得到点击搜索图标的代码为:
desc("搜索").findOne().click();
心细的你可能注意到了,这个控件还有很多其它的属性,例如checked,className,clickable等等,为什么不用这些属性来定位搜索图标呢?答案是,其它控件也有这些值相同的属性、尝试一下你就可以发现很多其他控件的checked属性和搜索控件一样都是
false
,如果我们用checked(false)
作为条件,将会找到很多控件,而无法确定哪一个是搜索图标。因此,要找到我们想要的那个控件,选择器的条件通常需要是可唯一确定控件的。我们通常用一个独一无二的属性来定位一个控件,例如这个例子中就没有其他控件的desc(描述)属性为"搜索"另外,对于这个搜索图标而言,id属性也是唯一的,我们也可以用
id("action_search").findOne().click()
来点击这个控件。如果一个控件有id属性,那么这个属性很可能是唯一的,除了以下几种情况:
- QQ的控件的 id 属性很多都是"name",也就是在QQ界面难以通过 id 来定位一个控件
- 列表中的控件,比如QQ联系人列表,微信联系人列表等
尽管id属性很方便,但也不总是最方便的,例如对于微信和网易云音乐,每次更新它的控件 id 都会变化,导致了相同代码对于不同版本的微信、网易云音乐并不兼容,除了这些属性外,主要还有以下几种属性:
className
类名。类名表示一个控件的类型,例如文本控件为"android.widget.TextView", 图片控件为"android.widget.ImageView"等packageName
包名。包名表示控件所在的应用包名,例如QQ界面的控件的包名为"com.tencent.mobileqq"bounds
控件在屏幕上的范围drawingOrder
控件在父控件的绘制顺序indexInParent
控件在父控件的位置clickable
控件是否可点击longClickable
控件是否可长按checkable
控件是否可勾选checked
控件是否可已勾选scrollable
控件是否可滑动selected
控件是否已选择editable
控件是否可编辑visibleToUser
控件是否可见enabled
控件是否已启用depth
控件的布局深度有时候只靠一个属性并不能唯一确定一个控件,这时需要通过属性的组合来完成定位,例如
className("ImageView").depth(10).findOne().click()
,通过链式调用来组合条件通常用这些技巧便可以解决大部分问题,即使解决不了问题,也可以通过布局分析的"生成代码"功能来尝试生成一些选择器代码。接下来的问题便是对选取的控件进行操作,包括:
click()
点击。点击一个控件,前提是这个控件的clickable属性为truelongClick()
长按。长按一个控件,前提是这个控件的longClickable属性为truesetText()
设置文本,用于编辑框控件设置文本。scrollForward(),
scrollBackward()
滑动。滑动一个控件(列表等), 前提是这个控件的scrollable属性为trueexits()
判断控件是否存在waitFor()
等待控件出现这些操作包含了绝大部分控件操作。根据这些我们可以很容易写出一个"刷屏"脚本(代码仅为示例,请不要在别人的群里测试,否则容易被踢!!!
while(true){ className("EditText").findOne().setText("刷屏..."); text("发送").findOne().clicK(); }
上面这段代码也可以写成:
while(true){ className("EditText").setText("刷屏..."); text("发送").clicK(); }
如果不加
findOne()
而直接进行操作,则选择器会找出所有符合条件的控件并操作另外一个比较常用的操作—滑动。滑动操作的第一步是找到需要滑动的控件,例如要滑动QQ消息列表则在悬浮窗布局层次分析中找到AbsListView
,这个控件就是消息列表控件,如下图:长按可查看控件信息,注意到其scrollable属性为true,并找出其id为"recent_chat_list",从而下滑QQ消息列表的代码为:
id("recent_chat_list").className("AbsListView").findOne().scrollForward();
scrollForward()
为向前滑,包括下滑和右滑。选择器的入门教程暂且到这里,更多信息可以查看下面的文档和选择器进阶具体常用的一些方法:
查找控件1.UiSelector.find()
- 返回 UiCollection
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,找到所有满足条件的控件集合并返回,这个搜索只进行一次,并不保证一定会找到,因而会出现返回的控件集合为空的情况
不同于
findOne()
或者findOnce()
只找到一个控件并返回一个控件,find()
函数会找出所有满足条件的控件并返回一个控件集合,之后可以对控件集合进行操作可以通过empty()函数判断找到的是否为空,例如:
var c = className("ListView").find(); c.scrollDown(); if(c.empty()){ toast("没找到╭(╯^╰)╮"); }else{ toast("找到啦!"); }
2.UiSelector.findOne()
- 返回 UiObject
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件,如果找不到控件,当屏幕内容发生变化时会重新寻找,直至找到
需要注意的是,如果屏幕上一直没有出现所描述的控件,则该函数会阻塞,直至所描述的控件出现为止,因此此函数不会返回
null
该函数本来应该命名为
untilFindOne()
,但由于历史遗留原因已经无法修改,如果想要只在屏幕上搜索一次而不是一直搜索,请使用findOnce()
另外,如果屏幕上有多个满足条件的控件,
findOne()
采用深度优先搜索(DFS),会返回该搜索算法找到的第一个控件,注意控件找到的顺序有时会起到作用3.UiSelector.findOne(timeout)
timeout
{number} 搜索的超时时间,单位毫秒- 返回 UiObject
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件;如果在timeout毫秒的时间内没有找到符合条件的控件,则终止搜索并返回
null
该函数类似于不加参数的
findOne()
,只不过加上了时间限制示例:
//启动Auto.js launchApp("Auto.js"); //在6秒内找出日志图标的控件 var w = id("action_log").findOne(6000); //如果找到控件则点击 if(w != null){ w.click(); }else{ //否则提示没有找到 toast("没有找到日志图标"); }
4.UiSelector.findOnce()
- 返回 UiObject
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,如果找到符合条件的控件则返回该控件;否则返回
null
5.UiSelector.findOnce(i)
i
{number} 索引根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,并返回第 i+1 个符合条件的控件;如果没有找到符合条件的控件,或者符合条件的控件个数 < i,则返回
null。
注意这里的控件次序,是由搜索算法深度优先搜索(DSF)决定的6.UiSelector.text(str)
str
{string} 控件文本- 返回 {UiSelector} 返回选择器自身以便链式调用
为当前选择器附加控件"text等于字符串str"的筛选条件
控件的 text(文本)属性是文本控件上的显示的文字,例如微信左上角的"微信"文本
7.UiSelector.className(str)
str
{string} 控件文本- 返回 {UiSelector} 返回选择器自身以便链式调用
为当前选择器附加控件"className等于字符串str"的筛选条件
控件的className(类名)表示一个控件的类别,例如文本控件的类名为android.widget.TextView
如果一个控件的类名以"android.widget."开头,则可以省略这部分,例如文本控件可以直接用
className("TextView")
的选择器常见控件的类名如下:
android.widget.TextView
文本控件android.widget.ImageView
图片控件android.widget.Button
按钮控件android.widget.EditText
输入框控件android.widget.AbsListView
列表控件android.widget.LinearLayout
线性布局android.widget.FrameLayout
帧布局android.widget.RelativeLayout
相对布局android.widget.RelativeLayout
相对布局android.support.v7.widget.RecyclerView
通常也是列表控件案例:微信朋友圈不断往下滚动
while(true){ className("android.widget.ListView").findOne().scrollDown() };
8.UiSelector.id(resId)
resId
{string} 控件的id,以"包名:id/"开头,例如"com.tencent.mm:id/send_btn",也可以不指定包名,这时会以当前正在运行的应用的包名来补全id,例如id("send_btn"),在QQ界面想当于id("com.tencent.mobileqq:id/send_btn")为当前选择器附加"id等于resId"的筛选条件
控件的id属性通常是可以用来确定控件的唯一标识,如果一个控件有id,那么使用id来找到他是最好的方法。要查看屏幕上的控件的id,可以开启悬浮窗并使用界面工具,点击相应控件即可查看,若查看到的控件id为null,表示该控件没有id。另外,在列表中会出现多个控件的id相同的情况,例如微信的联系人列表,每个头像的id都是一样的,此时不能用id来唯一确定控件
在QQ界面经常会出现多个id为"name"的控件,在微信上则每个版本的id都会变化,对于这些软件而言比较难用id定位控件
九.UiObject
UiObject表示一个控件,可以通过这个对象获取到控件的属性,也可以对控件进行点击、长按等操作
获取一个UiObject通常通过选择器的
findOne(),
findOnce()
等函数,也可以通过UiCollection来获取,或者通过UiObject.child(),
UiObject.parent()
等函数来获取一个控件的子控件或父控件1.children()
- 返回 UiCollection
返回该控件的所有子控件组成的控件集合。可以用于遍历一个控件的子控件,例如:
className("AbsListView").findOne().children() .forEach(function(child){ log(child.className()); });
2.childCount()
- 返回 {number}
返回子控件数目
3.child(i)
- i {number} 子控件索引
- 返回 {UiObject}
返回第 i+1 个子控件。如果 i>=控件数目或者小于0,则抛出异常。
需要注意的是,由于布局捕捉的问题,该函数可能返回
null
,也就是可能获取不到某个子控件遍历子控件的示例:
var list = className("AbsListView").findOne(); for(var i = 0; i < list.childCount(); i++){ var child = list.child(i); log(child.className()); }
4.bounds()
- 返回 Rect
返回控件在屏幕上的范围,其值是一个Rect对象(存储坐标信息)
示例:
var b = text("Auto.js").findOne().bounds(); toast("控件在屏幕上的范围为" + b);
如果一个控件本身无法通过
click()
点击,那么我们可以利用bounds()
函数获取其坐标,再利用坐标点击,例如:var b = desc("打开侧拉菜单").findOne().bounds(); click(b.centerX(), b.centerY()); //如果使用root权限,则用 Tap(b.centerX(), b.centerY());
5.UiObject.longClick()
- 返回 {Boolean}
长按该控件,并返回是否点击成功,如果该函数返回false,可能是该控件不可点击(longClickable为false),或者是因为当前界面无法响应该点击等
6.UiObject.setText(text)
text
{string} 文本- 返回 {Boolean}
设置输入框控件的文本内容,并返回是否设置成功,该函数只对可编辑的输入框(editable为true)有效
7.UiObject.scrollUp()
对集合中所有控件执行向上滑的操作,并返回是否全部操作成功
8.UiObject.scrollDown()
对集合中所有控件执行向下滑的操作,并返回是否全部操作成功
9.UiObject.scrollLeft()
对集合中所有控件执行向左滑的操作,并返回是否全部操作成功
10.UiObject.scrollRight()
对集合中所有控件执行向右滑的操作,并返回是否全部操作成功
11.id()
- 返回 {string}
获取控件的id,如果一个控件没有id,则返回
null
12.text()
- 返回 {string}
获取控件的文本,如果控件没有文本,返回
""