UiAutomator常用类之UI手势动作

        UiAutomator中执行一个UI相关操作,比如click、swipe、drag等,都需要借助两个类,即GestureController、Gestures、PointerGesture。

        Gestures翻译成中文就是手势。所谓手势,其实就是指用户手指或触摸笔在触摸屏上连续触碰行为,比如在屏幕上从左至右划出一个动作,就是手势。

        PointerGesture就是代表,当操作一个手势时,其包含的一个点的动作。简单点理解就是,手势,比如滑动,它是连续的,可以看成是很多点连续组成的结果。而PointerGesture就代表手势执行过程中的一个点上的动作。即Gestures包含了很多PointerGesture。

        一个Gestures的动作包括很多PointerGesture。所以,Gestures提供了构建PointerGesture的工厂方法。

        我的理解就是android把UI操作抽象成了手势Gestures。所以Gestures中封装了点击、滑动等动作。

一、PointerAction  表示手指手势中的某一个动作,是一个抽象接口

        一个手指手势PointerGesture包括多个PointerAction动作,比如一个手指手势包括:点->滑动等。PointerAction就是指的这其中一个动作。这些动作组合起来就变成手指手势PointerGesture了。

        包括手势起始点start、手势结束点end和持续时间duaratioin。并且还有一个插值接口函数interpolate()作用是:在持续时间duaration内,在起始点到结束点之间插入多个点Point。

/** A {@link PointerAction} represents part of a {@link PointerGesture}. */
private static abstract class PointerAction {
    final Point start;
    final Point end;
    final long duration;

    public PointerAction(Point startPoint, Point endPoint, long time) {
        start = startPoint;
        end = endPoint;
        duration = time;
    }

    public abstract Point interpolate(float fraction);
}

        PointerAction共有两个实现类PointerLinearMoveAction和PointerPauseAction。

        1、PointerLinearMoveAction会利用interpolate()来插入start~end区间内多个点Point,来模拟手势的move滑动行为

/**
 * A {@link PointerLinearMotionAction} moves the pointer between two points at a constant
 * speed.
 */
private static class PointerLinearMoveAction extends PointerAction {

    public PointerLinearMoveAction(Point startPoint, Point endPoint, int speed) {
        super(startPoint, endPoint, (long)(1000 * calcDistance(startPoint, endPoint) / speed));
    }

    @Override
    public Point interpolate(float fraction) {
        Point ret = new Point(start);
        ret.offset((int)(fraction * (end.x - start.x)), (int)(fraction * (end.y - start.y)));
        return ret;
    }

    private static double calcDistance(final Point a, final Point b) {
        return Math.sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
    }
}

        2、PointerPauseAction中的interpolate()插入的点和起始点start一样,从而可以模拟长按行为;

/** A {@link PointerPauseAction} holds the pointer steady for the given amount of time. */
private static class PointerPauseAction extends PointerAction {

    public PointerPauseAction(Point startPoint, long time) {
        super(startPoint, startPoint, time);
    }

    @Override
    public Point interpolate(float fraction) {
        return new Point(start);
    }
}

二、PointerGesture  手指的手势  关键类

        手指手势类PointerGesture类表示的是一个手指在屏幕上做出的手势。 PointerGesture内部维护了一个双向列表,保存了手指运动过程中所有的手指动作PointerAction

        1、PointerGesture对象中使用双端队列Deque mActions来存储组成手势的所有actions动作;

          2、使用long类型的mDelay表示延迟;

         3、使用long类型的mDuration表示持续时间

1、构造器

        当只是click操作,则使用第一个构造器即可;当手势为滑动等包含多个点操作时,需要使用第二个构造器,mDelay延迟会影响滑动手势的PointerAction的插入点的数量。滑动距离相同的情况下,延迟越大,插值器插入的点越少。

        可以看到PointerGesture的构造器其实是创建了一个PointerAction并保存到双向队列中第一个位置。代表手势的起始动作。

class PointerGesture {
    // The list of actions that make up this gesture.
    private Deque mActions = new ArrayDeque();
    private long mDelay;
    private long mDuration;

    /** Constructs a PointerGesture which touches down at the given start point. */
    public PointerGesture(Point startPoint) {
        this(startPoint, 0);
    }

    /**
     * Constructs a PointerGesture which touches down at the given start point after a given delay.
     * Used in multi-point gestures when the pointers do not all touch down at the same time.
     */
    public PointerGesture(Point startPoint, long initialDelay) {
        if (initialDelay < 0) {
            throw new IllegalArgumentException("initialDelay cannot be negative");
        }
        mActions.addFirst(new PointerPauseAction(startPoint, 0));
        mDelay = initialDelay;
    }

}

2、pause()  长按,代表的是持续双向队列中最后一个action一段时间

/** Adds an action which pauses for the specified amount of {@code time} in milliseconds. */
public PointerGesture pause(long time) {
    if (time < 0) {
        throw new IllegalArgumentException("time cannot be negative");
    }
    mActions.addLast(new PointerPauseAction(mActions.peekLast().end, time));
    mDuration += (mActions.peekLast().duration);
    return this;
}

3、move() 移动

/** Adds an action that moves the pointer to {@code dest} at {@code speed} pixels per second. */
public PointerGesture move(Point dest, int speed) {
    mActions.addLast(new PointerLinearMoveAction(mActions.peekLast().end, dest, speed));
    mDuration += (mActions.peekLast().duration);
    return this;
}

4、pointAt()获取手势中某个时间节点的点Point

        这里会取出双向队列保存的所有action,然后计算出该action的point。不同的PointAction在这里调用了其插值器函数interpolate()来计算当前时间滑动到什么位置。

/** Returns the pointer location at {@code time} milliseconds into this gesture. */
public Point pointAt(long time) {
    if (time < 0) {
        throw new IllegalArgumentException("Time cannot be negative");
    }
    time -= mDelay;
    for (PointerAction action : mActions) {
        if (time < action.duration) {
            return action.interpolate((float)time / action.duration);
        }
        time -= action.duration;
    }
    return mActions.peekLast().end;
}

三、Gestures 手势

        Gestures仅仅是在PointerGesture上层再封装一层。某些手势动作比如click,Gestures仅仅是返回一个PointerGesture, 有些动作比如多个手指同时操作捏合,则Gestures返回该动作的多个PointerGesture。

1、构造器

        Gestures的构造器是私有的,无法直接创建。所以Gestures其实是单例。需要借助UiDevice对象来创建Gestures对象。构建Gestures需要ViewConfiguration,这个需要用到context,而context又需要借助UiDeivce。

// Keep a handle to the ViewConfiguration
private ViewConfiguration mViewConfig;

// Private constructor.
private Gestures(ViewConfiguration config) {
   mViewConfig = config;
}

/** Returns the {@link Gestures} instance for the given {@link Context}. */
public static Gestures getInstance(UiDevice device) {
    if (sInstance == null) {
        Context context = device.getInstrumentation().getContext();
        sInstance = new Gestures(ViewConfiguration.get(context));
    }

    return sInstance;
}

2、点击click

        返回的是单个PointerGesture,持续时间不知道则为0。

/** Returns a {@link PointerGesture} representing a click at the given {@code point}. */
public PointerGesture click(Point point) {
    // A basic click is a touch down and touch up over the same point with no delay.
    return click(point, 0);
}

/**
 * Returns a {@link PointerGesture} representing a click at the given {@code point} that lasts
 * for {@code duration} milliseconds.
 *
 * @param point The point to click.
 * @param duration The duration of the click in milliseconds.
 * @return The {@link PointerGesture} representing this click.
 */
public PointerGesture click(Point point, long duration) {
    // A click is a touch down and touch up over the same point with an optional delay inbetween
    return new PointerGesture(point).pause(duration);
}

3、滑动

/**
 * Returns a {@link PointerGesture} representing a swipe.
 *
 * @param start The touch down point for the swipe.
 * @param end The touch up point for the swipe.
 * @param speed The speed at which to move in pixels per second.
 * @return The {@link PointerGesture} representing this swipe.
 */
public PointerGesture swipe(Point start, Point end, int speed) {
    // A swipe is a click that moves before releasing the pointer.
    return click(start).move(end, speed);
}

四、GestureController 手势控制

        执行给定的PointerGesture中双端队列Deque mActions保存的所有的手势动作行为。为了执行手势,该方法会记录手势中经过的每个点的位置,并向系统中插入MotionEvent事件,从而达到UI的效果。

public void performGesture(PointerGesture ... gestures){}

如果是单指操作,则gestures就是有当前手指的手势对象;

如果是多指操作,则gestures是多个手指的手势对象

        函数内部逻辑大致为

1、使用一个map来存储每个手指的手势和手势的起始点Pointer对象;

2、初始化一个优先队列active,用来在循环中存储所有手指的PointerGesture,其排序按照谁先开始谁放前面的规则

3、使用一个优先队列pending来存储存储所有手指的PointerGesture,其排序按照哪个手指的PointerGesture先结束谁放前面的规则

4、使用elapsedTime来统计手势动作已经执行多次时间;

5、用一个while循环依次取出pending中所有的PointerGesture,封装成MotionEvent.ACTION_DOWN时间并插入到系统中,从而触发了每个手指的down点击事件;

6、再用一个while循环遍历优先队列active中每个PointerGesture,判断当前时间是否已经超过了PointerGesture的手势持续时间,如果超过了则封装一个MotionEvent.ACTION_UP事件,并插入到系统中,代表某个手指离开屏幕;

7、当某个手势还没有结束,则计算出滑动需要经过的所有点,封装出MotionEvent.ACTION_MOVE事件,开始滑动。

public void performGesture(PointerGesture ... gestures) {
    // Initialize pointers
    int count = 0;
    Map pointers = new HashMap();
    for (PointerGesture g : gestures) {
        pointers.put(g, new Pointer(count++, g.start()));
    }

    // Initialize MotionEvent arrays
    List properties = new ArrayList();
    List     coordinates = new ArrayList();

    // Track active and pending gestures
    PriorityQueue active = new PriorityQueue(gestures.length,
            END_TIME_COMPARATOR);
    PriorityQueue pending = new PriorityQueue(gestures.length,
            START_TIME_COMPARATOR);
    pending.addAll(Arrays.asList(gestures));

    // Record the start time
    long startTime = SystemClock.uptimeMillis();

    // Loop
    MotionEvent event;
    for (long elapsedTime = 0; !pending.isEmpty() || !active.isEmpty();
            elapsedTime = SystemClock.uptimeMillis() - startTime) {

        // Touchdown any new pointers
        while (!pending.isEmpty() && elapsedTime > pending.peek().delay()) {
            PointerGesture gesture = pending.remove();
            Pointer pointer = pointers.get(gesture);

            // Add the pointer to the MotionEvent arrays
            properties.add(pointer.prop);
            coordinates.add(pointer.coords);

            // Touch down
            int action = MotionEvent.ACTION_DOWN;
            if (!active.isEmpty()) {
                // Use ACTION_POINTER_DOWN for secondary pointers. The index is stored at
                // ACTION_POINTER_INDEX_SHIFT.
                action = MotionEvent.ACTION_POINTER_DOWN
                        + ((properties.size() - 1) << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
            }
            event = getMotionEvent(startTime, startTime + elapsedTime, action, properties,
                    coordinates);
            getDevice().getUiAutomation().injectInputEvent(event, true);

            // Move the PointerGesture to the active list
            active.add(gesture);
        }

        // Touch up any completed pointers
        while (!active.isEmpty()
                && elapsedTime > active.peek().delay() + active.peek().duration()) {

            PointerGesture gesture = active.remove();
            Pointer pointer = pointers.get(gesture);

            // Update pointer positions
            pointer.updatePosition(gesture.end());
            for (PointerGesture current : active) {
                pointers.get(current).updatePosition(current.pointAt(elapsedTime));
            }

            int action = MotionEvent.ACTION_UP;
            int index = properties.indexOf(pointer.prop);
            if (!active.isEmpty()) {
                action = MotionEvent.ACTION_POINTER_UP
                        + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
            }
            event = getMotionEvent(startTime, startTime + elapsedTime, action, properties,
                    coordinates);
            getDevice().getUiAutomation().injectInputEvent(event, true);

            properties.remove(index);
            coordinates.remove(index);
        }

        // Move any active pointers
        for (PointerGesture gesture : active) {
            Pointer pointer = pointers.get(gesture);
            pointer.updatePosition(gesture.pointAt(elapsedTime - gesture.delay()));

        }
        if (!active.isEmpty()) {
            event = getMotionEvent(startTime, startTime + elapsedTime, MotionEvent.ACTION_MOVE,
                    properties, coordinates);
            getDevice().getUiAutomation().injectInputEvent(event, true);
        }
    }
}

五、总结

PointerAction:代表 手势中的某个动作;

PointerGesture:代表 一个手指在屏幕上操作的完整手势;

Gestures: 在PointerGesture基础上封装了一层,可以表示多个手指的PointerGesture;

GestureController:根据Gestures真正执行动作的类

网上有篇文章写的也不错,值得参考下:

Android之UiAutomator测试框架源码分析(第29篇:UiObject2中的点击控件功能深度分析)_叫我王员外就行的博客-CSDN博客_uiautomator2 源码分析前言UI自动化的三个重要组成部分为查找控件、操作控件、预期结果,UiObject2是如何做到操作控件的?通过本篇源码分析,我们将知道插装测试的点击原理https://blog.csdn.net/cadi2011/article/details/106739947

你可能感兴趣的:(Appium,java,ui)