【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 不得不说两句,过了这么久才再次更新博 ...
随机推荐
- http://www.111cn.net/jsp/Jsp-Servlet/45158.htm
http://www.111cn.net/jsp/Jsp-Servlet/45158.htm
- FireBug使用总结
FireBug是一个用于网站前端的开发工具,它是Firefox浏览器的一个拓展插件,它可以调试JavaScript.查看DOM.分析CSS.监控网络流量以及进行Ajax交互等.提供了几乎前端开发需要的 ...
- BZOJ2610 : [Poi2003]Monkeys
考虑离线,将删边操作倒过来变成加边,等价于询问每个点什么时候与1连通 使用并查集维护,每次合并时如果有一边是1所在连通块,就把另一边的所有点的答案更新 #include<cstdio> # ...
- 避免常见的6种HTML5错误用法
一.不要使用section作为div的替代品 人们在标签使用中最常见到的错误之一就是随意将HTML5的<section>等价于<div>——具体地说,就是直接用作替代品(用于样 ...
- TYVJ P1073 加分二叉树 Label:区间dp
背景 NOIP2003 提高组 第三道 描述 设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号.每个节点都有一个分数(均为正整数),记第j个节点的 ...
- 【BZOJ】1084: [SCOI2005]最大子矩阵(DP)
http://www.lydsy.com/JudgeOnline/problem.php?id=1084 有一个1A--- 本题没看懂,,不会啊囧..感觉完全设不了状态..看了题解,囧,m<=2 ...
- 使用Python中的urlparse、urllib抓取和解析网页(一)(转)
对搜索引擎.文件索引.文档转换.数据检索.站点备份或迁移等应用程序来说,经常用到对网页(即HTML文件)的解析处理.事实上,通过Python 语言提供的各种模块,我们无需借助Web服务器或者Web浏览 ...
- Shell 之数组 [转]
本文也即<Learning the bash Shell>3rd Edition的第六章Command-Line Options and Typed varilables之读书笔记之三,但 ...
- MIB-II
. 1.3.6.1.2.1
- 前端编码规范(2)—— HTML 规范
HTML 规范 文档类型 推荐使用 HTML5 的文档类型申明: <!DOCTYPE html>. (建议使用 text/html 格式的 HTML.避免使用 XHTML.XHTML 以及 ...