• 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。

    1. /** A {@link PointerAction} represents part of a {@link PointerGesture}. */
    2. private static abstract class PointerAction {
    3. final Point start;
    4. final Point end;
    5. final long duration;
    6. public PointerAction(Point startPoint, Point endPoint, long time) {
    7. start = startPoint;
    8. end = endPoint;
    9. duration = time;
    10. }
    11. public abstract Point interpolate(float fraction);
    12. }

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

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

    1. /**
    2. * A {@link PointerLinearMotionAction} moves the pointer between two points at a constant
    3. * speed.
    4. */
    5. private static class PointerLinearMoveAction extends PointerAction {
    6. public PointerLinearMoveAction(Point startPoint, Point endPoint, int speed) {
    7. super(startPoint, endPoint, (long)(1000 * calcDistance(startPoint, endPoint) / speed));
    8. }
    9. @Override
    10. public Point interpolate(float fraction) {
    11. Point ret = new Point(start);
    12. ret.offset((int)(fraction * (end.x - start.x)), (int)(fraction * (end.y - start.y)));
    13. return ret;
    14. }
    15. private static double calcDistance(final Point a, final Point b) {
    16. return Math.sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
    17. }
    18. }

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

    1. /** A {@link PointerPauseAction} holds the pointer steady for the given amount of time. */
    2. private static class PointerPauseAction extends PointerAction {
    3. public PointerPauseAction(Point startPoint, long time) {
    4. super(startPoint, startPoint, time);
    5. }
    6. @Override
    7. public Point interpolate(float fraction) {
    8. return new Point(start);
    9. }
    10. }

    二、PointerGesture  手指的手势  关键类

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

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

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

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

    1、构造器

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

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

    1. class PointerGesture {
    2. // The list of actions that make up this gesture.
    3. private Deque mActions = new ArrayDeque();
    4. private long mDelay;
    5. private long mDuration;
    6. /** Constructs a PointerGesture which touches down at the given start point. */
    7. public PointerGesture(Point startPoint) {
    8. this(startPoint, 0);
    9. }
    10. /**
    11. * Constructs a PointerGesture which touches down at the given start point after a given delay.
    12. * Used in multi-point gestures when the pointers do not all touch down at the same time.
    13. */
    14. public PointerGesture(Point startPoint, long initialDelay) {
    15. if (initialDelay < 0) {
    16. throw new IllegalArgumentException("initialDelay cannot be negative");
    17. }
    18. mActions.addFirst(new PointerPauseAction(startPoint, 0));
    19. mDelay = initialDelay;
    20. }
    21. }

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

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

    3、move() 移动

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

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

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

    1. /** Returns the pointer location at {@code time} milliseconds into this gesture. */
    2. public Point pointAt(long time) {
    3. if (time < 0) {
    4. throw new IllegalArgumentException("Time cannot be negative");
    5. }
    6. time -= mDelay;
    7. for (PointerAction action : mActions) {
    8. if (time < action.duration) {
    9. return action.interpolate((float)time / action.duration);
    10. }
    11. time -= action.duration;
    12. }
    13. return mActions.peekLast().end;
    14. }

    三、Gestures 手势

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

    1、构造器

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

    1. // Keep a handle to the ViewConfiguration
    2. private ViewConfiguration mViewConfig;
    3. // Private constructor.
    4. private Gestures(ViewConfiguration config) {
    5. mViewConfig = config;
    6. }
    7. /** Returns the {@link Gestures} instance for the given {@link Context}. */
    8. public static Gestures getInstance(UiDevice device) {
    9. if (sInstance == null) {
    10. Context context = device.getInstrumentation().getContext();
    11. sInstance = new Gestures(ViewConfiguration.get(context));
    12. }
    13. return sInstance;
    14. }

    2、点击click

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

    1. /** Returns a {@link PointerGesture} representing a click at the given {@code point}. */
    2. public PointerGesture click(Point point) {
    3. // A basic click is a touch down and touch up over the same point with no delay.
    4. return click(point, 0);
    5. }
    6. /**
    7. * Returns a {@link PointerGesture} representing a click at the given {@code point} that lasts
    8. * for {@code duration} milliseconds.
    9. *
    10. * @param point The point to click.
    11. * @param duration The duration of the click in milliseconds.
    12. * @return The {@link PointerGesture} representing this click.
    13. */
    14. public PointerGesture click(Point point, long duration) {
    15. // A click is a touch down and touch up over the same point with an optional delay inbetween
    16. return new PointerGesture(point).pause(duration);
    17. }

    3、滑动

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

    四、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事件,开始滑动。

    1. public void performGesture(PointerGesture ... gestures) {
    2. // Initialize pointers
    3. int count = 0;
    4. Map pointers = new HashMap();
    5. for (PointerGesture g : gestures) {
    6. pointers.put(g, new Pointer(count++, g.start()));
    7. }
    8. // Initialize MotionEvent arrays
    9. List properties = new ArrayList();
    10. List coordinates = new ArrayList();
    11. // Track active and pending gestures
    12. PriorityQueue active = new PriorityQueue(gestures.length,
    13. END_TIME_COMPARATOR);
    14. PriorityQueue pending = new PriorityQueue(gestures.length,
    15. START_TIME_COMPARATOR);
    16. pending.addAll(Arrays.asList(gestures));
    17. // Record the start time
    18. long startTime = SystemClock.uptimeMillis();
    19. // Loop
    20. MotionEvent event;
    21. for (long elapsedTime = 0; !pending.isEmpty() || !active.isEmpty();
    22. elapsedTime = SystemClock.uptimeMillis() - startTime) {
    23. // Touchdown any new pointers
    24. while (!pending.isEmpty() && elapsedTime > pending.peek().delay()) {
    25. PointerGesture gesture = pending.remove();
    26. Pointer pointer = pointers.get(gesture);
    27. // Add the pointer to the MotionEvent arrays
    28. properties.add(pointer.prop);
    29. coordinates.add(pointer.coords);
    30. // Touch down
    31. int action = MotionEvent.ACTION_DOWN;
    32. if (!active.isEmpty()) {
    33. // Use ACTION_POINTER_DOWN for secondary pointers. The index is stored at
    34. // ACTION_POINTER_INDEX_SHIFT.
    35. action = MotionEvent.ACTION_POINTER_DOWN
    36. + ((properties.size() - 1) << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
    37. }
    38. event = getMotionEvent(startTime, startTime + elapsedTime, action, properties,
    39. coordinates);
    40. getDevice().getUiAutomation().injectInputEvent(event, true);
    41. // Move the PointerGesture to the active list
    42. active.add(gesture);
    43. }
    44. // Touch up any completed pointers
    45. while (!active.isEmpty()
    46. && elapsedTime > active.peek().delay() + active.peek().duration()) {
    47. PointerGesture gesture = active.remove();
    48. Pointer pointer = pointers.get(gesture);
    49. // Update pointer positions
    50. pointer.updatePosition(gesture.end());
    51. for (PointerGesture current : active) {
    52. pointers.get(current).updatePosition(current.pointAt(elapsedTime));
    53. }
    54. int action = MotionEvent.ACTION_UP;
    55. int index = properties.indexOf(pointer.prop);
    56. if (!active.isEmpty()) {
    57. action = MotionEvent.ACTION_POINTER_UP
    58. + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
    59. }
    60. event = getMotionEvent(startTime, startTime + elapsedTime, action, properties,
    61. coordinates);
    62. getDevice().getUiAutomation().injectInputEvent(event, true);
    63. properties.remove(index);
    64. coordinates.remove(index);
    65. }
    66. // Move any active pointers
    67. for (PointerGesture gesture : active) {
    68. Pointer pointer = pointers.get(gesture);
    69. pointer.updatePosition(gesture.pointAt(elapsedTime - gesture.delay()));
    70. }
    71. if (!active.isEmpty()) {
    72. event = getMotionEvent(startTime, startTime + elapsedTime, MotionEvent.ACTION_MOVE,
    73. properties, coordinates);
    74. getDevice().getUiAutomation().injectInputEvent(event, true);
    75. }
    76. }
    77. }

    五、总结

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

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

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

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

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

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

  • 相关阅读:
    un7.29:Linux——centos中如何安装与配置redis?
    -树的高度-
    springboot系列(三):多环境切换,实例演示|超级详细,建议收藏
    java计算机毕业设计济南旅游网站MyBatis+系统+LW文档+源码+调试部署
    机器学习-(手推)线性回归-最小二乘法(矩阵表达)、几何意义
    三面“有赞”Java岗斩获offer:Spring+JVM+并发锁+分布式+算法
    Ansible 企业级自动化运维实战
    美发店微信小程序怎么制作
    第六十一章 符号概览
    python+flask计算机毕业设计热点推荐个性化新闻系统(程序+开题+论文)
  • 原文地址:https://blog.csdn.net/liuqinhou/article/details/125991566