UiAutomator 请问使用 UiAutomator 时如何精确滑动到某一坐标点?

Lu · 2017年02月28日 · 最后由 Lu 回复于 2017年03月03日 · 4117 次阅读

看了有一个 public boolean drag(int startX, int startY, int endX, int endY, int steps) 的方法,但是貌似只能模拟出拖动到坐标(endX,endY)时抬手,并不能精确停在那一点,有什么办法做到吗?主要想在 listview 或者 gridview 中一次完全滑动上去一屏,但无论是用 swipe 或者 drag 的办法都无法准确做到正好移动一屏😓

共收到 14 条回复 时间 点赞

一种,你可以用 flingForward() 代替 drag,不过你的控件必须是 UiScrollable 的
另一种,重写 swipe
extras\android\m2repository\com\android\support\test\uiautomator\uiautomator-v18\2.1.2\uiautomator-v18-2.1.2-sources.jar!\android\support\test\uiautomator\InteractionController.java

public boolean swipe(int downX, int downY, int upX, int upY, int steps, boolean drag) {
    boolean ret = false;
    int swipeSteps = steps;
    double xStep = 0;
    double yStep = 0;

    // avoid a divide by zero
    if(swipeSteps == 0)
        swipeSteps = 1;

    xStep = ((double)(upX - downX)) / swipeSteps;
    yStep = ((double)(upY - downY)) / swipeSteps;

    // first touch starts exactly at the point requested
    ret = touchDown(downX, downY);
    if (drag)
        SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
    for(int i = 1; i < swipeSteps; i++) {
        ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i));
        if(ret == false)
            break;
        // set some known constant delay between steps as without it this
        // become completely dependent on the speed of the system and results
        // may vary on different devices. This guarantees at minimum we have
        // a preset delay.
        SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
    }
    if (drag)
        SystemClock.sleep(REGULAR_CLICK_LENGTH);
    ret &= touchUp(upX, upY);
    return(ret);
}

把 SystemClock.sleep(REGULAR_CLICK_LENGTH);这一行的 REGULAR_CLICK_LENGTH 改成 mUiAutomatorBridge.getSystemLongPressTime()

第二种我没试过,仅仅理论上可行

Lu #2 · 2017年03月01日 Author

回复得好快😃 多谢多谢,我一会儿试一下,回头反馈结果

Lu #3 · 2017年03月01日 Author

貌似,还没使出来,第一种 flingForward() 无法指定滑动的距离,而第二个方法理论上应该可行,但是估计得重新写 jar 包才成,里面的方法都是 private 的,继承后改写时遇到各种各样的问题,我弄了半天完全搞不定😓
我应用的情景是: 有一个 gridview 的相册,我需要先点选一屏所显示的所有照片,然后将相册 gridview 向上滑动,然后让下一屏幕中的第一行没有被点选的照片的最顶边正好顶头显示,这样 index = 0 时所代表的元素就是未曾选取的新照片了。因此需要精确控制上滑的距离,否则就会出现向上滑动屏幕后,上一屏幕的最后一行的照片继续出现屏幕的顶部,这时候再对 index = 0 进行操作时,就是勾选掉之前被选中的照片。

目前采用的笨办法为,在 drag(int startX, int startY, int endX, int endY, int steps) 中把 steps 值变大,这样移动得会特别慢,使得在(endX, endY)后惯性前滑的距离很近,从而达到需要的效果。文档里说每个 step 的单位是 0.5ms,我设定的是 400,也就是 2 秒,滑动得果然慢慢悠悠的,但是从滑动开始到滑动静止,好像用的时间要比 2 秒大不少😨

不可以打开手机坐标功能,直接显示坐标点?

换一种思路,在点了第一张图之后,用 key 来移动,比如 pressDown 之类的

重写不代表你要替换 jar 包里原来的功能,你可以自己写一个 draghold 方法,你只要能调用 touchMove/Down/Up 就行,仿照原来的写法

如果是为了翻页点控件的话,楼主不妨试试 AccessibilityNodeInfo。暴力的一触即达

Lu #7 · 2017年03月01日 Author

谢谢楼上各位朋友点拨,我得好好消化消化,一一鼓捣鼓捣。
@264768502 下午一直在试怎么能访问到 touchdown/touchmove/touchup,不过没鼓捣出来😓 😓
@wangpengtcl 我可以用 UiAutomator View 查到需要的起始坐标和终点坐标,就是卡在了无法精确控制移动停止在指定的终点坐标上,不过会不会是我没理解你的建议呢?
@wixed AccessibilityNodeInfo 这个方法我去查查怎么用,第一次听说,貌似很直爽的方式,哈哈

截了 4 张图放在下面:
第一张图是这个 gridview 相册在最顶时的样子,青花瓷老鼠那张图片的左上角坐标是(362,216)
第二张图是我希望的滑动效果,即生肖羊正好替代青花瓷老鼠的位置,也就是说,gridview 从(362, 1664)往上滑动 1448 个像素点
而第三、四张图片是各种不成功的 drag、swipe 操作后的效果,在第三张图片里,index = 1 时本来应该是生肖羊的,结果是青花瓷猪;而在第四张图片里时,index = 1 的生肖羊被往上移动了太多,这样 for 循环多次自动滑屏后,差值被不断累加,有可能就会直接漏过一行图片



家里没法看源码,明天可以看看怎么 import
另外又想到一个 api
swipe(Point[] segments, int segmentSteps)
设 3 个点,第一个第二个用来拖拽到目标位置,第三个点可以横向拖

楼主,不要用 index,最好用 instance
另外

/*获取页面控件的根节点*/
 AccessibilityNodeInfo nodeInfo = UiAutomation.getRootInActiveWindow();
/*获取id为com.android.settings:id/category_content的集合*/
 List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("com.android.settings:id/category_content");

然后就是遍历 list 元素里的 child,找出你要的 child,接着执行

/*调用AccessibilityNodeInfo的performAction方法就可以执行点击了*/
AccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Lu #10 · 2017年03月02日 Author

谢谢...试了这个方法,但还是不成,代码片段我是这么写的
Point p1 = new Point();
Point p2 = new Point();
Point p3 = new Point();
p1.x = 362;
p1.y = 1670;
p2.x = 362;
p2.y = 222;
p3.x = 600;
p3.y = 222;
Point[] args = { p1, p2, p3};
UiDevice.getInstance().swipe(args, 100);

结果(362, 1670)这个点并没有被滑动到(362, 222),而是停在了(362,237),感觉好奇怪

貌似这个方法在测试滑动解锁时很有用,但会不会滑动解锁时不需要精确到 1 个像素,位置差不多就成,所以才会这样的吗?好奇怪。

重要发现:
我看了一下,既然 237 和 222 差了 15 个像素,也就是偏差值为 15,222-15 = 207,于是我把 p2 和 p3 的纵坐标定为了 207,
Point p1 = new Point();
Point p2 = new Point();
Point p3 = new Point();
p1.x = 362;
p1.y = 1670;
p2.x = 362;
p2.y = 207;
p3.x = 600;
p3.y = 207;
Point[] args = { p1, p2, p3};
UiDevice.getInstance().swipe(args, 10);

貌似这回成了,这个时候,只要 steps 的数值不是太小都会停留在(362,222)这个位置。我试的 steps 的范围是从 10 到 1000,当然如果太小,比如 steps=1,那么界面根本不会滑动了。

留下的疑问:
这种先滑动一次,然后找差值,然后再根据差值来做补偿的方法,难道真的是在所有机器上都适用的方法吗?
借了同事的小米 max 做了下同样的测试,遇到了下面的报错,没解决了,所以回头我试完了再继续补充




Lu #11 · 2017年03月02日 Author

真机没有借到,用虚拟机试了一下,分辨率为 720x1280

目标:
将左上角坐标为(180,1188)的图标往上移动,在其左上角移动到(180,108)。

代码:

// 下面是针对 夜神模拟器的
Point p1 = new Point();
Point p2 = new Point();
Point p3 = new Point();
p1.x = 180;
p1.y = 1188;
p2.x = 180;
p2.y = 108;
p3.x = 600;
p3.y = 108;
Point[] args = { p1, p2, p3};
UiDevice.getInstance().swipe(args, 10);

结果:
图标的左上角只从(180,1188)移动到了(180,120),跟设定的纵坐标 y =108 相差 12,与前一次真机测试的 13 相差不大。

个人的猜测:
使用 swipe(Point[] segments, int segmentSteps) 时的偏移值是 13 个像素左右,原因不明。

试验的截图:




Lu #12 · 2017年03月02日 Author
wixed 回复

谢谢,以后我注意用 instance 代替 index,不过 AccessibilityNodeInfo 方法如何运用我一直在看,所以没有及时回复,请问有这方面的 demo 吗😵

Lu 回复

我这个也是从别人那学来的,你可以去看看这个帖子
https://testerhome.com/topics/7273
虽然是 robotium。但是 uiautomator 用的也是 UiAutomation。
/获取页面控件的根节点/
AccessibilityNodeInfo nodeInfo = UiAutomation.getRootInActiveWindow();
上面这行代码实际上是获取了 Activity 的根节点,你要点的图片什么的就在这个根节点下的子节点里,所以你可以通过 findAccessibilityByView 或者 findAccessibilityByText 获取指定的节点集,后面的事就是去找你要的那个节点,然后点击就行了

Lu #14 · 2017年03月03日 Author
wixed 回复

😃 多谢多谢,小白去研究研究😵

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册