【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 不得不说两句,过了这么久才再次更新博 ...
随机推荐
- 阿里云SDK手册之java SDK
进行阿里云sdk开发的前提是已经购买阿里云的相关服务才能调用阿里的相关接口进行开发.最近公司在做云管控的项目,于是进行下摘录总结. 一. 环境准备 阿里云针对不同的开发语言提供不同的sdk,由于项目用 ...
- 洛谷 P1019 单词接龙 Label:dfs
题目描述 单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合 ...
- javascript中字符串常用操作总结、JS字符串操作大全
字符串的操作在js中非常频繁,也非常重要.以往看完书之后都能记得非常清楚,但稍微隔一段时间不用,便会忘得差不多,记性不好是硬伤啊...今天就对字符串的一些常用操作做个整理,一者加深印象,二者方便今后温 ...
- 【wikioi】1033 蚯蚓的游戏问题(费用流)
http://wikioi.com/problem/1033/ 这题也是很水的费用流啊,同之前那题一样,拆点然后建边,容量为1,费用为点权.然后建个源连第一行每个点,容量为1,费用为0,然后最后一行每 ...
- 安装win7 ubuntu双系统
http://www.cnblogs.com/shengansong/archive/2011/10/23/2221716.html http://www.cnblogs.com/shenganson ...
- Eclipse远程调试(远程服务器端监听)
前提:远程服务器上运行的WEB项目class对应的源码与本地项目中必须保持一致,也就是远程tomcat部署的项目就是本机项目打包过去的,而本机项目没有发生变动. 远程服务器端 服务器端配置eclips ...
- Html - Footer
通用的Footer代码片段 <style> #footer { padding: 20px; text-align: center; background-color: #666; bor ...
- C# - 事物回滚
该功能依赖dbhelp.cs 传送门:http://www.cnblogs.com/CyLee/p/5324652.html 起始点,不能放在try语句中 this.DbHelp.BeginTrans ...
- php如何判断远程文件是否存在
<?php /* 函数:remote_file_exists 功能:判断远程文件是否存在 参数: $url_file -远程文件URL 返回:存在返回true,不存在或者其他原因 ...
- 下载站中的下载连接其实是php脚本文件控制
什么是php文件,PHP是一种服务器端HTML-嵌入式脚本描述语言. 其最强大和最重要的特征是其数据库集成层,使用它完成一个含有数据库功能的网页是不可置信的简单.在HTML文件中, PHP脚本程序(语 ...