热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Android测试】【随笔】模拟双指点击

◆版权声明:本文出自胖喵~的博客,转载必须注明出处。转载请注明出处:http:www.cnblogs.comby-dreamp5258660.html手势

 版权声明:本文出自胖喵~的博客,转载必须注明出处。

    转载请注明出处: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
     * 
     *       PointerCoords p = new PointerCoords();
     *       p.x = stepX;
     *       p.y = stepY;
     *       p.pressure = 1;
     *       p.size = 1;
     * 
     * @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 true if all points on all paths are injected successfully, false
     *        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 )
            maxSteps = (maxSteps  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 ) {
            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 ) {
            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 ) {
            // for each pointer
            for (int x = 0; 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 )
            pointerCoords[x] = touches[x][touches[x].length - 1];

        // touch up
        for (int x = 1; 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 数组中实现它所有的步骤,顺着轨迹指针的路径进行模拟触摸,调用者是能够指定进行完成复杂的手势,例如画圈,不规则形状,其中每个点都可以有不同的路径。(解释的我自己都看不下去了)

 

  废话不说了,直接上我的实战代码吧:

 

  1 private void zoomin() {
  2         UiDevice mdevice = getUiDevice();
  3         int h = mdevice.getDisplayHeight();
  4         int w = mdevice.getDisplayWidth();
  5         System.out.println("h: " + h + " w: " + w);
  6 
  7         MultiPointerGesture(w / 2 - 100, h / 2 - 100, w / 2 + 100, h / 2 + 100);
  8     }
  9 
 10     private void MultiPointerGesture(int x1, int y1, int x2, int y2) {
 11         PointerProperties[] properties = new PointerProperties[2];
 12         PointerProperties pp1 = new PointerProperties();
 13         pp1.id = 0;
 14         pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
 15         PointerProperties pp2 = new PointerProperties();
 16         pp2.id = 1;
 17         pp2.toolType = MotionEvent.TOOL_TYPE_FINGER;
 18         properties[0] = pp1;
 19         properties[1] = pp2;
 20 
 21         PointerCoords[] pointerCoords = new PointerCoords[2];
 22         PointerCoords pc1 = new PointerCoords();
 23         pc1.pressure = 1;
 24         pc1.size = 1;
 25         pc1.x = x1;
 26         pc1.y = y1;
 27         PointerCoords pc2 = new PointerCoords();
 28         pc2.pressure = 1;
 29         pc2.size = 1;
 30         pc2.x = x1;
 31         pc2.y = y1;
 32         pointerCoords[0] = pc1;
 33         pointerCoords[1] = pc2;
 34 
 35         PointerProperties[] properties2 = new PointerProperties[2];
 36         PointerProperties pp12 = new PointerProperties();
 37         pp12.id = 0;
 38         pp12.toolType = MotionEvent.TOOL_TYPE_FINGER;
 39         PointerProperties pp22 = new PointerProperties();
 40         pp22.id = 1;
 41         pp22.toolType = MotionEvent.TOOL_TYPE_FINGER;
 42         properties2[0] = pp12;
 43         properties2[1] = pp22;
 44 
 45         PointerCoords[] pointerCoords2 = new PointerCoords[2];
 46         PointerCoords pc12 = new PointerCoords();
 47         pc12.pressure = 1;
 48         pc12.size = 1;
 49         pc12.x = x2;
 50         pc12.y = y2;
 51         PointerCoords pc22 = new PointerCoords();
 52         pc22.pressure = 1;
 53         pc22.size = 1;
 54         pc22.x = x2;
 55         pc22.y = y2;
 56         pointerCoords2[0] = pc12;
 57         pointerCoords2[1] = pc22;
 58 
 59         PointerCoords[][] ppCoords = new PointerCoords[2][];
 60         ppCoords[0] = pointerCoords;
 61         ppCoords[1] = pointerCoords2;
 62 
 63         UiDevice device = UiDevice.getInstance();
 64 
 65         Class UiDevice_class = UiDevice.class;
 66         Field field_UiD;
 67         try
 68         {
 69             field_UiD = UiDevice_class.getDeclaredField("mUiAutomationBridge");
 70             field_UiD.setAccessible(true);
 71             Object uiAutomatorBridge;
 72 
 73             uiAutomatorBridge = field_UiD.get(device);
 74 
 75             Class tmp = Class.forName("com.android.uiautomator.core.UiAutomatorBridge");
 76             Field field = tmp.getDeclaredField("mInteractionController");
 77             field.setAccessible(true);
 78             Object interactiOnController= field.get(uiAutomatorBridge);
 79 
 80             Class ijClass = interactionController.getClass();
 81             Method method = null;
 82             try
 83             {
 84                 method = ijClass.getDeclaredMethod("performMultiPointerGesture", new Class[] { PointerCoords[][].class });
 85             } catch (NoSuchMethodException e)
 86             {
 87                 // method =
 88                 // ijClass.getDeclaredMethod("performMultiPointerGesture", new
 89                 // Class[]{PointerCoords[].class);
 90             }
 91             method.setAccessible(true);
 92             method.invoke(interactionController, new Object[] { ppCoords });
 93 
 94         } catch (NoSuchFieldException e)
 95         {
 96             e.printStackTrace();
 97         } catch (SecurityException e)
 98         {
 99             e.printStackTrace();
100         } catch (IllegalArgumentException e)
101         {
102             e.printStackTrace();
103         } catch (IllegalAccessException e)
104         {
105             e.printStackTrace();
106         } catch (InvocationTargetException e)
107         {
108             e.printStackTrace();
109         } catch (ClassNotFoundException e1)
110         {
111             e1.printStackTrace();
112         }
113     }

  可以看到当时花了很大的代价,都用到了反射机制,最终发现Uiautomator对外暴露了这个接口,真是让人哭笑不得啊。。

 


推荐阅读
  • 工作经验谈之-让百度地图API调用数据库内容 及详解
    这段时间,所在项目中要用到的一个模块,就是让数据库中的内容在百度地图上展现出来,如经纬度。主要实现以下几点功能:1.读取数据库中的经纬度值在百度上标注出来。2.点击标注弹出对应信息。3 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 基于移动平台的会展导游系统APP设计与实现的技术介绍与需求分析
    本文介绍了基于移动平台的会展导游系统APP的设计与实现过程。首先,对会展经济和移动互联网的概念进行了简要介绍,并阐述了将会展引入移动互联网的意义。接着,对基础技术进行了介绍,包括百度云开发环境、安卓系统和近场通讯技术。然后,进行了用户需求分析和系统需求分析,并提出了系统界面运行流畅和第三方授权等需求。最后,对系统的概要设计进行了详细阐述,包括系统前端设计和交互与原型设计。本文对基于移动平台的会展导游系统APP的设计与实现提供了技术支持和需求分析。 ... [详细]
  • mui框架offcanvas侧滑超出部分隐藏无法滚动如何解决
    web前端|js教程off-canvas,部分,超出web前端-js教程mui框架中off-canvas侧滑的一个缺点就是无法出现滚动条,因为它主要用途是设置类似于qq界面的那种格 ... [详细]
  • java.lang.Class.getDeclaredMethod()方法java.lang.Class.getDeclaredMethod()方法用法实例教程-方法返回一个Met ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • HTML学习02 图像标签的使用和属性
    本文介绍了HTML中图像标签的使用和属性,包括定义图像、定义图像地图、使用源属性和替换文本属性。同时提供了相关实例和注意事项,帮助读者更好地理解和应用图像标签。 ... [详细]
  • C#多线程解决界面卡死问题的完美解决方案
    当界面需要在程序运行中不断更新数据时,使用多线程可以解决界面卡死的问题。一个主线程创建界面,使用一个子线程执行程序并更新主界面,可以避免卡死现象。本文分享了一个例子,供大家参考。 ... [详细]
  • 4554:[Tjoi2016&Heoi2016]游戏 ... [详细]
  • Shodan简单用法Shodan简介Shodan是互联网上最可怕的搜索引擎,与谷歌不同的是,Shodan不是在网上搜索网址,而是直接进入互联网的背后通道。Shodan可以说是一款“ ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了shp与json互转(转载)相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文实例讲述了Android编程实现读取工程中的txt文件功能。分享给大家供大家参考,具体如下:1.众所周知,Android的res文件夹 ... [详细]
author-avatar
mobiledu2502869467
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有