【Android测试】【随笔】模拟双指点击
◆版权声明:本文出自胖喵~的博客,转载必须注明出处。
转载请注明出处:http://www.cnblogs.com/by-dream/p/5258660.html
手势
看到这个标题,很多人会想一想 “双指点击” 的操作是什么样的,首先解释一下吧,为了能清晰明了一点,请看下面的图:
左上角的Tap代表点击操作,也就是我们说的 “单指单击”;右上角是Double Tap顾名思义,使用一个手指完成 “双击” 的动作;左下角的Scroll代表的是用一个手指完成 “滑动”的动作;最后看右下角这张图,这个动作就是我们本节要讲的内容,用两个手指完成 “单击” 的动作,注意两个手指点击的实际要同时,同时按下,同时抬起。
什么需求
为什么会有这样的需求呢?这个需求可能大部分人都没遇到过,就是目前市面上的三大地图(腾讯、百度、高德)的底图都支持了多种缩放效果,其中“双指点击”的操作就是让底图缩小一个层级,如下所示:
当然 “双指点击 ”的操作也可以延伸为 “双指滑动 ”的操作,这个可能会在游戏里面用的多吧,当然这个后续有需求了再介绍。
思考
于是我得思考用什么方式实现呢?上一节 《【Android测试】【随笔】模拟长按电源键》中介绍了一种getevent/sendevent 的方法,那么是否可以通过getevent录制手势,然后用sendevent回放呢?于是我就去尝试了一下,结果失败了。失败的原因是我那两个不够灵活的手指在同时按下的时候总是微微的移动一下,很难模拟出只有按下和抬起的操作,于是回放的时候总是不成功。试了好多次都不行,最终决定尝试其他的方式。抱着试试看的态度,去看了看Uiautomator的源码,结果就找到了要的答案。
Uiautomator源码
我的Uiautomator系列的博客中,没有向其他工具一样介绍源码分析的部分,原因是实在太忙,没时间去细看,草草了事分享给大家又怕把大家带入误区,刚好这里我就简单的说说。首先要下载的同学可以点击这里下载。
下载后我们可以在 ..\uiautomator\core\com\android\uiautomator\core 的路径下看到这些代码。
通过仔细的寻找,在 InteractionController.java 文件中找到了一个看起来很好用的一个方法 performMultiPointerGesture :
/**
* Performs a multi-touch gesture
*
* Takes a series of touch coordinates for at least 2 pointers. Each pointer must have
* all of its touch steps defined in an array of {@link PointerCoords}. By having the ability
* to specify the touch points along the path of a pointer, the caller is able to specify
* complex gestures like circles, irregular shapes etc, where each pointer may take a
* different path.
*
* To create a single point on a pointer's touch path
* <code>
* PointerCoords p = new PointerCoords();
* p.x = stepX;
* p.y = stepY;
* p.pressure = 1;
* p.size = 1;
* </code>
* @param touches each array of {@link PointerCoords} constitute a single pointer's touch path.
* Multiple {@link PointerCoords} arrays constitute multiple pointers, each with its own
* path. Each {@link PointerCoords} in an array constitute a point on a pointer's path.
* @return <code>true</code> if all points on all paths are injected successfully, <code>false
* </code>otherwise
* @since API Level 18
*/
public boolean performMultiPointerGesture(PointerCoords[] ... touches) {
boolean ret = true;
if (touches.length < 2) {
throw new IllegalArgumentException("Must provide coordinates for at least 2 pointers");
} // Get the pointer with the max steps to inject.
int maxSteps = 0;
for (int x = 0; x < touches.length; x++)
maxSteps = (maxSteps < touches[x].length) ? touches[x].length : maxSteps; // specify the properties for each pointer as finger touch
PointerProperties[] properties = new PointerProperties[touches.length];
PointerCoords[] pointerCoords = new PointerCoords[touches.length];
for (int x = 0; x < touches.length; x++) {
PointerProperties prop = new PointerProperties();
prop.id = x;
prop.toolType = MotionEvent.TOOL_TYPE_FINGER;
properties[x] = prop; // for each pointer set the first coordinates for touch down
pointerCoords[x] = touches[x][0];
} // Touch down all pointers
long downTime = SystemClock.uptimeMillis();
MotionEvent event;
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1,
properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= injectEventSync(event); for (int x = 1; x < touches.length; x++) {
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
getPointerAction(MotionEvent.ACTION_POINTER_DOWN, x), x + 1, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= injectEventSync(event);
} // Move all pointers
for (int i = 1; i < maxSteps - 1; i++) {
// for each pointer
for (int x = 0; x < touches.length; x++) {
// check if it has coordinates to move
if (touches[x].length > i)
pointerCoords[x] = touches[x][i];
else
pointerCoords[x] = touches[x][touches[x].length - 1];
} event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_MOVE, touches.length, properties, pointerCoords, 0, 0, 1, 1,
0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); ret &= injectEventSync(event);
SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
} // For each pointer get the last coordinates
for (int x = 0; x < touches.length; x++)
pointerCoords[x] = touches[x][touches[x].length - 1]; // touch up
for (int x = 1; x < touches.length; x++) {
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
getPointerAction(MotionEvent.ACTION_POINTER_UP, x), x + 1, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= injectEventSync(event);
} Log.i(LOG_TAG, "x " + pointerCoords[0].x);
// first to touch down is last up
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 1,
properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= injectEventSync(event);
return ret;
}
对这个方法解释一下吧:模拟一个多触摸的手势。至少两个点进行一系列的触摸操作,每一个滚动轨迹的点集都需要使用在 PointerCoords 数组中实现它所有的步骤,顺着轨迹指针的路径进行模拟触摸,调用者是能够指定进行完成复杂的手势,例如画圈,不规则形状,其中每个点都可以有不同的路径。(解释的我自己都看不下去了)
废话不说了,直接上我的实战代码吧:
private void zoomin() {
UiDevice mdevice = getUiDevice();
int h = mdevice.getDisplayHeight();
int w = mdevice.getDisplayWidth();
System.out.println("h: " + h + " w: " + w); MultiPointerGesture(w / 2 - 100, h / 2 - 100, w / 2 + 100, h / 2 + 100);
} private void MultiPointerGesture(int x1, int y1, int x2, int y2) {
PointerProperties[] properties = new PointerProperties[2];
PointerProperties pp1 = new PointerProperties();
pp1.id = 0;
pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
PointerProperties pp2 = new PointerProperties();
pp2.id = 1;
pp2.toolType = MotionEvent.TOOL_TYPE_FINGER;
properties[0] = pp1;
properties[1] = pp2; PointerCoords[] pointerCoords = new PointerCoords[2];
PointerCoords pc1 = new PointerCoords();
pc1.pressure = 1;
pc1.size = 1;
pc1.x = x1;
pc1.y = y1;
PointerCoords pc2 = new PointerCoords();
pc2.pressure = 1;
pc2.size = 1;
pc2.x = x1;
pc2.y = y1;
pointerCoords[0] = pc1;
pointerCoords[1] = pc2; PointerProperties[] properties2 = new PointerProperties[2];
PointerProperties pp12 = new PointerProperties();
pp12.id = 0;
pp12.toolType = MotionEvent.TOOL_TYPE_FINGER;
PointerProperties pp22 = new PointerProperties();
pp22.id = 1;
pp22.toolType = MotionEvent.TOOL_TYPE_FINGER;
properties2[0] = pp12;
properties2[1] = pp22; PointerCoords[] pointerCoords2 = new PointerCoords[2];
PointerCoords pc12 = new PointerCoords();
pc12.pressure = 1;
pc12.size = 1;
pc12.x = x2;
pc12.y = y2;
PointerCoords pc22 = new PointerCoords();
pc22.pressure = 1;
pc22.size = 1;
pc22.x = x2;
pc22.y = y2;
pointerCoords2[0] = pc12;
pointerCoords2[1] = pc22; PointerCoords[][] ppCoords = new PointerCoords[2][];
ppCoords[0] = pointerCoords;
ppCoords[1] = pointerCoords2; UiDevice device = UiDevice.getInstance(); Class UiDevice_class = UiDevice.class;
Field field_UiD;
try
{
field_UiD = UiDevice_class.getDeclaredField("mUiAutomationBridge");
field_UiD.setAccessible(true);
Object uiAutomatorBridge; uiAutomatorBridge = field_UiD.get(device); Class tmp = Class.forName("com.android.uiautomator.core.UiAutomatorBridge");
Field field = tmp.getDeclaredField("mInteractionController");
field.setAccessible(true);
Object interactionController = field.get(uiAutomatorBridge); Class ijClass = interactionController.getClass();
Method method = null;
try
{
method = ijClass.getDeclaredMethod("performMultiPointerGesture", new Class[] { PointerCoords[][].class });
} catch (NoSuchMethodException e)
{
// method =
// ijClass.getDeclaredMethod("performMultiPointerGesture", new
// Class[]{PointerCoords[].class);
}
method.setAccessible(true);
method.invoke(interactionController, new Object[] { ppCoords }); } catch (NoSuchFieldException e)
{
e.printStackTrace();
} catch (SecurityException e)
{
e.printStackTrace();
} catch (IllegalArgumentException e)
{
e.printStackTrace();
} catch (IllegalAccessException e)
{
e.printStackTrace();
} catch (InvocationTargetException e)
{
e.printStackTrace();
} catch (ClassNotFoundException e1)
{
e1.printStackTrace();
}
}
可以看到当时花了很大的代价,都用到了反射机制,最终发现Uiautomator对外暴露了这个接口,真是让人哭笑不得啊。。
【Android测试】【随笔】模拟双指点击的更多相关文章
- android 代码实现模拟用户点击、滑动等操作
/** * 模拟用户点击 * * @param view 要触发操作的view * @param x 相对于要操作view的左上角x轴偏移量 * @param y 相对于要操作view的左上角y轴偏移 ...
- 【Android测试】【随笔】模拟长按电源键
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/5195121.html 起因 昨天群里看到有人问如何实现一个 ...
- Android单元测试与模拟测试详解
测试与基本规范 为什么需要测试? 为了稳定性,能够明确的了解是否正确的完成开发. 更加易于维护,能够在修改代码后保证功能不被破坏. 集成一些工具,规范开发规范,使得代码更加稳定( 如通过 phabri ...
- Android自动化框架 模拟操作 模拟测试
转自:http://bbs2.c114.net/home.php?mod=space&uid=1025779&do=blog&id=5322 几种常见的Android自动化测试 ...
- 【Android测试】【随笔】Android Studio环境搭建
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/5482778.html 随着Android Studio的推 ...
- 【Android测试】【随笔】获得App的包名和启动页Activity
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/5157308.html 前言 经常看到一些刚刚接触Andro ...
- Android Studio学习随笔-基本事件(点击)
最常见的点击事件有三种创建方法,在MainActivity.java的onCreate函数(在启动程序是优先运行的程序)中创建setOnClickListener(动态运行)(最常见) protect ...
- android 测试 Monkey 和 MonkeyRunner 的使用
一.Monkey的使用 Monkey使用起来比较简单,简而言之就是模拟手机点击效果,随机发送N个点击动作给手机,主要对于程序的稳定和承受压力的测试. 1.首先连接上你的手机或者启动模拟器: 2.运行C ...
- 【Android测试】【第九节】MonkeyRunner—— 初识
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/4836815.html 不得不说两句,过了这么久才再次更新博 ...
随机推荐
- 简单几何(直线求交点) POJ 2074 Line of Sight
题目传送门 题意:从一条马路(线段)看对面的房子(线段),问连续的能看到房子全部的最长区间 分析:自己的思路WA了:先对障碍物根据坐标排序,然后在相邻的障碍物的间隔找到区间,这样还要判断是否被其他障碍 ...
- HDU1247 Hat’s Words(Trie树)
常规做法是枚举每个字符串每个位置,时间复杂度O(n*len*len),(建字典树O(n*len)). 然而我看这题第一眼想的是时间复杂度O(n*len)的算法..就是建正反两棵字典树,每个字符串跑分别 ...
- Unity Built-in Shader详解三
上次讲的是Transparent Shader Family,他们是绘制半透明的对象使用的,但他们并不能满足我们全部的要求. Transparent Cutout Shader Family是对半透明 ...
- python zip enumerate函数
zip是一个内置函数, 接受两个或多个序列,并将他们拉到一起,成为一个元组列表.每个元组包含各个序列中的一个元素. s = 'abc' t = [0,1,2] zip(s,t) >>> ...
- TYVJ P1020 导弹拦截 Label:水
题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某天,雷达捕捉到敌国的导弹 ...
- 【NOIP2003】传染病控制(-贪心/dfs)
我自己yy了个贪心算法,在某oj 0msAC~.然后去wikioi提交,呵呵,原来是之前oj的数据太弱给我水过了,我晕. 我之前的想法是在这棵树上维护sum,然后按时间来割边,每一时刻割已经感染的人所 ...
- Codeforces Round# 305 (Div 1)
[Codeforces 547A] #include <bits/stdc++.h> #define maxn 1000010 using namespace std; typedef l ...
- BZOJ4129: Haruna’s Breakfast
Description Haruna每天都会给提督做早餐! 这天她发现早饭的食材被调皮的 Shimakaze放到了一棵 树上,每个结点都有一样食材,Shimakaze要考验一下她. 每个食材都有一个美 ...
- Reprojection Matrix Q
Given the disparity d and 2D point (x, y) , we can derive the 3D depth using the 4-by-4 reprojection ...
- stringstream 使用方法
C++中的stringstream是专门用来处理字符串流的,可以按顺序将string或int都拼接起来,而不用把int转换为string格式,使用方法如下: #include <string&g ...