Android UiAutomator2 pinch捏合缩放手势源码探究

公冶伟
2023-12-01

只能在AndroidTest里运行

UiDevice myDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        try {
            myDevice.findObject(new UiSelector().text("element text to be found")).pinchIn(50, 10); //to zoom in
            myDevice.findObject(new UiSelector().text("element text to be found")).pinchOut(50, 20); //to zoom out
        } catch (UiObjectNotFoundException e) {
            e.printStackTrace();
        }

接下来,走到pinchIn里:

//UiObject.class
public boolean pinchIn(int percent, int steps) throws UiObjectNotFoundException {
        percent = percent < 0 ? 0 : (percent > 100 ? 100 : percent);
        float percentage = (float)percent / 100.0F;
        AccessibilityNodeInfo node = this.findAccessibilityNodeInfo(this.mConfig.getWaitForSelectorTimeout());
        if (node == null) {
            throw new UiObjectNotFoundException(this.mUiSelector.toString());
        } else {
            Rect rect = this.getVisibleBounds(node);
            if (rect.width() <= 40) {
                throw new IllegalStateException("Object width is too small for operation");
            } else {
                Point startPoint1 = new Point(rect.centerX() - (int)((float)(rect.width() / 2) * percentage), rect.centerY());
                Point startPoint2 = new Point(rect.centerX() + (int)((float)(rect.width() / 2) * percentage), rect.centerY());
                Point endPoint1 = new Point(rect.centerX() - 20, rect.centerY());
                Point endPoint2 = new Point(rect.centerX() + 20, rect.centerY());
                return this.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
            }
        }
    }

public boolean performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1, Point endPoint2, int steps) {
        if (steps == 0) {
            steps = 1;
        }

        float stepX1 = (float)((endPoint1.x - startPoint1.x) / steps);
        float stepY1 = (float)((endPoint1.y - startPoint1.y) / steps);
        float stepX2 = (float)((endPoint2.x - startPoint2.x) / steps);
        float stepY2 = (float)((endPoint2.y - startPoint2.y) / steps);
        int eventX1 = startPoint1.x;
        int eventY1 = startPoint1.y;
        int eventX2 = startPoint2.x;
        int eventY2 = startPoint2.y;
        PointerCoords[] points1 = new PointerCoords[steps + 2];
        PointerCoords[] points2 = new PointerCoords[steps + 2];

        PointerCoords p2;
        for(int i = 0; i < steps + 1; ++i) {
            p2 = new PointerCoords();
            p2.x = (float)eventX1;
            p2.y = (float)eventY1;
            p2.pressure = 1.0F;
            p2.size = 1.0F;
            points1[i] = p2;
            PointerCoords p2 = new PointerCoords();
            p2.x = (float)eventX2;
            p2.y = (float)eventY2;
            p2.pressure = 1.0F;
            p2.size = 1.0F;
            points2[i] = p2;
            eventX1 = (int)((float)eventX1 + stepX1);
            eventY1 = (int)((float)eventY1 + stepY1);
            eventX2 = (int)((float)eventX2 + stepX2);
            eventY2 = (int)((float)eventY2 + stepY2);
        }

        PointerCoords p1 = new PointerCoords();
        p1.x = (float)endPoint1.x;
        p1.y = (float)endPoint1.y;
        p1.pressure = 1.0F;
        p1.size = 1.0F;
        points1[steps + 1] = p1;
        p2 = new PointerCoords();
        p2.x = (float)endPoint2.x;
        p2.y = (float)endPoint2.y;
        p2.pressure = 1.0F;
        p2.size = 1.0F;
        points2[steps + 1] = p2;
        return this.performMultiPointerGesture(points1, points2);
    }

    public boolean performMultiPointerGesture(PointerCoords[]... touches) {
        return this.getInteractionController().performMultiPointerGesture(touches);
    }
//InteractionController.class
public boolean performMultiPointerGesture(PointerCoords[]... touches) {
        boolean ret = true;
        if (touches.length < 2) {
            throw new IllegalArgumentException("Must provide coordinates for at least 2 pointers");
        } else {
            int maxSteps = 0;

            for(int x = 0; x < touches.length; ++x) {
                maxSteps = maxSteps < touches[x].length ? touches[x].length : maxSteps;
            }

            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 = Configurator.getInstance().getToolType();
                properties[x] = prop;
                pointerCoords[x] = touches[x][0];
            }

            long downTime = SystemClock.uptimeMillis();
            MotionEvent event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), 0, 1, properties, pointerCoords, 0, 0, 1.0F, 1.0F, 0, 0, 4098, 0);
            ret &= this.injectEventSync(event);

            int x;
            for(x = 1; x < touches.length; ++x) {
                event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), this.getPointerAction(5, x), x + 1, properties, pointerCoords, 0, 0, 1.0F, 1.0F, 0, 0, 4098, 0);
                ret &= this.injectEventSync(event);
            }

            for(x = 1; x < maxSteps - 1; ++x) {
                for(int x = 0; x < touches.length; ++x) {
                    if (touches[x].length > x) {
                        pointerCoords[x] = touches[x][x];
                    } else {
                        pointerCoords[x] = touches[x][touches[x].length - 1];
                    }
                }

                event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), 2, touches.length, properties, pointerCoords, 0, 0, 1.0F, 1.0F, 0, 0, 4098, 0);
                ret &= this.injectEventSync(event);
                SystemClock.sleep(5L);
            }

            for(x = 0; x < touches.length; ++x) {
                pointerCoords[x] = touches[x][touches[x].length - 1];
            }

            for(x = 1; x < touches.length; ++x) {
                event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), this.getPointerAction(6, x), x + 1, properties, pointerCoords, 0, 0, 1.0F, 1.0F, 0, 0, 4098, 0);
                ret &= this.injectEventSync(event);
            }

            Log.i(LOG_TAG, "x " + pointerCoords[0].x);
            event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), 1, 1, properties, pointerCoords, 0, 0, 1.0F, 1.0F, 0, 0, 4098, 0);
            ret &= this.injectEventSync(event);
            return ret;
        }
    }

private boolean injectEventSync(InputEvent event) {
        return this.getUiAutomation().injectInputEvent(event, true);
    }

看起来,getUiAutomation()的injectInputEvent,也可以使用 ‘android.hardware.input.InputManager’的injectInputEvent去做,接受的参数也是InputEvent类型。

pinchOut:

public boolean pinchOut(int percent, int steps) throws UiObjectNotFoundException {
        percent = percent < 0 ? 1 : (percent > 100 ? 100 : percent);
        float percentage = (float)percent / 100.0F;
        AccessibilityNodeInfo node = this.findAccessibilityNodeInfo(this.mConfig.getWaitForSelectorTimeout());
        if (node == null) {
            throw new UiObjectNotFoundException(this.mUiSelector.toString());
        } else {
            Rect rect = this.getVisibleBounds(node);
            if (rect.width() <= 40) {
                throw new IllegalStateException("Object width is too small for operation");
            } else {
                Point startPoint1 = new Point(rect.centerX() - 20, rect.centerY());
                Point startPoint2 = new Point(rect.centerX() + 20, rect.centerY());
                Point endPoint1 = new Point(rect.centerX() - (int)((float)(rect.width() / 2) * percentage), rect.centerY());
                Point endPoint2 = new Point(rect.centerX() + (int)((float)(rect.width() / 2) * percentage), rect.centerY());
                return this.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
            }
        }
    }

直接在Activity里用UIAutomator的UIDevice里,报RuntimeException:

Process: com.example.myapplication, PID: 14409
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.myapplication/com.example.myapplication.MainActivity}: java.lang.IllegalStateException: UiDevice singleton not initialized
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3895)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4074)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:91)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:149)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:103)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2473)
        at android.os.Handler.dispatchMessage(Handler.java:110)
        at android.os.Looper.loop(Looper.java:219)
        at android.app.ActivityThread.main(ActivityThread.java:8347)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
     Caused by: java.lang.IllegalStateException: UiDevice singleton not initialized
        at androidx.test.uiautomator.UiDevice.getInstance(UiDevice.java:249)
        at com.example.myapplication.MainActivity.onCreate(MainActivity.java:17)
        at android.app.Activity.performCreate(Activity.java:8085)
        at android.app.Activity.performCreate(Activity.java:8073)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1320)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3868)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4074) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:91) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:149) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:103) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2473) 
        at android.os.Handler.dispatchMessage(Handler.java:110) 
        at android.os.Looper.loop(Looper.java:219) 
        at android.app.ActivityThread.main(ActivityThread.java:8347) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055) 

 类似资料: