◆版權聲明:本文出自胖喵~的博客,轉載必須注明出處。
轉載請注明出處: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 數組中實現它所有的步驟,順着軌跡指針的路徑進行模擬觸摸,調用者是能夠指定進行完成復雜的手勢,例如畫圈,不規則形狀,其中每個點都可以有不同的路徑。(解釋的我自己都看不下去了)
廢話不說了,直接上我的實戰代碼吧:
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對外暴露了這個接口,真是讓人哭笑不得啊。。