【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 不得不说两句,过了这么久才再次更新博 ...
随机推荐
- Codeforces Round #203 (Div. 2) A.TL
#include <iostream> #include <algorithm> using namespace std; int main(){ int n,m; cin & ...
- 【COGS】714. USACO 1.3.2混合牛奶(贪心+水题)
http://cojs.tk/cogs/problem/problem.php?pid=714 在hzwer的刷题记录上,默默地先跳过2题T_T...求凸包和期望的..T_T那是个啥..得好好学习 看 ...
- APIO2015
还没有写完APIO2015的题目,打算今天写一写. T1: 按位DP,DP时要保证已确定的位为0. 前4组设f[n][k]表示把前n个分成k组是否合法. 最后一组设g[n]表示把前n个最少分为多少组才 ...
- SpringMvc_快速入门,深入分析
目录 一.前言二.spring mvc 核心类与接口三.spring mvc 核心流程图 四.spring mvc DispatcherServlet说明 五.spring mvc 父子上下文的说明 ...
- [原]Android Studio查询SHA1的方法
前提:C:\Users\Administrator\AndroidStudioProjects文件夹中存在xxx.jks秘钥文件,比如: 进入Android Studio的Terminal: Micr ...
- jquery面试题里 缓存问题如何解决?
jquery面试题里 缓存问题如何解决? 如果直接用jQuery里的$.ajax()方法的话,去除缓存很简单,只需要配置一下缓存属性cache为false,但如果想要简单写法getJSON(),去除缓 ...
- QTabWiget Change Color 改变颜色
Qt中的QTabWiget 类提供了一个标签控件,但是这个控件默认初始化的颜色是白色,和原窗口的颜色不同,看起来非常的违和,所以我们希望将其的背景颜色设为当前窗口的背景颜色.我们所要做的就是先将应用程 ...
- swift文件上传及表单提交
var carData:NSMutableDictionary = NSMutableDictionary(); var request:NSMutableURLRequest = NSMutable ...
- mysql时该如何估算内存的消耗,公式如何计算?
经常有人问配置mysql时该如何估算内存的消耗.那么该使用什么公式来计算呢? 关心内存怎么使用的原因是可以理解的.如果配置mysql服务器使用太少的内存会导致性能不是最优的;如果配置了太多的内存则会导 ...
- 结合计划任务每天从Symantec官网下载离线病毒库
#三种方法,由初级到高级 $numbers = 1..40 | Foreach {"{0:D3}" -f $_} #将数字类型格式化并转换为字符串类型使用-f字符串操作符 ForE ...