多点触摸

MotionEvent.getActionMasked()

       常⻅值:

  • ACTION_DOWN 第⼀个⼿指按下(之前没有任何⼿指触摸到 View)
  • ACTION_UP 最后⼀个⼿指抬起(抬起之后没有任何⼿指触摸到 View,这个⼿指未必是 ACTION_DOWN 的那个⼿指)
  • ACTION_MOVE 有⼿指发⽣移动
  • ACTION_POINTER_DOWN 额外⼿指按下(按下之前已经有别的⼿指触摸到 View)
  • ACTION_POINTER_UP 有⼿指抬起,但不是最后⼀个(抬起之后,仍然还有别的⼿指在触
    摸着 View)

event.getActionIndex()

       获取抬起或者落下手指的 index 。如果现在屏幕上已经有一个手指,此时又按下一个手指,该方法返回1(index 从0开始计数);如果现在将第一个手指抬起,该方法会返回0,如果是第二个手指抬起,该方法会返回1。

       ACTION_DOWN 和 ACTION_UP 中,event.getActionIndex() 是没有意义的,而 ACTION_POINTER_DOWN 和 ACTION_POINTER_UP 中才有意义,得知是哪个 index 的手指按下或者抬起了。

触摸事件的结构

       触摸事件是按序列来分组的,每⼀组事件必然以 ACTION_DOWN 开头,以 ACTION_UP 或 ACTION_CANCEL 结束。

       ACTION_POINTER_DOWN 和 ACTION_POINTER_UP 和 ACTION_MOVE ⼀样,只是事件序列中的组成部分,并不会单独分出新的事件序列。

       触摸事件序列是针对 View 的,⽽不是针对 pointer 的。“某个 pointer 的事件”这种说法是不正确的。getX()/getY() 获取的是第0个 pointer 的横/纵坐标,getX()/getY() 与 getX(0)/getY(0) 是相等的。ACTION_MOVE 并不是某个点在移动,而 getX()/getY() 也不是获得移动的点的坐标。ACTION_MOVE 只会告诉有点移动了,而具体是那个点不清楚,也没有相关 API。

       同⼀时刻,⼀个 View 要么没有事件序列,要么只有⼀个事件序列。

        对于 ACTION_POINTER_DOWN ,手指刚刚触摸 View ,这个点便会被记录,比如原来有1个触摸点,此时便会变成2个,在后续的 ACTION_MOVE 会有2个点;在某一时刻抬起一个手指,(原来屏幕上有两个手指),ACTION_POINTER_UP 中,触摸点的数量还是2,而下一个 ACTION_MOVE 中,触摸点的数量才会变成1。

pointer 的 index 与 id

       当 pointerA 触摸到屏幕时,它的 index 为0,id 为0;此时 pointerB 触摸到屏幕, pointerA 依然是 index 为0,id 为0,而 pointerB 的 index 是1,id 为1;此时 pointerA 松开, pointerB 的 index 变为0,而 id 还是1。index 是用来遍历 pointer 的,追踪某个 pointer 使用 id 。

       id 是可以复用的,接着之前的场景,现在 pointerA 松开,pointerB 的 index 为0,id 为1,此时不存在 id 为0的 pointer 了。当有新的 pointer 按下时,此时并不会将新的 pointer 的 id 赋值为 2 ,而是将之前为0的 id 赋给新的 pointer ,而这个新的 pointer 可以是 pointerA 也可以不是。API会假设之前抬起的手指(pointerA)此时落下了,这只是一种简单的假设。由于 id 的复用,此时将之前的 index 也重新还给新的 pointer ,新 pointer 的 id 为0,index 为0,pointerB 的 index 为1,id 为1。

       下面看三个 pointer 的情况:pointerA、pointerB、pointerC 依次触摸屏幕,pointerA 的 id 和 index 分别是 0、0,pointerB 的 id 和 index 分别是 1、1,pointerC 的 id 和 index 分别是 2、2;此时抬起 pointerB ,pointerA 的 id 和 index 分别是 0、0,而 pointerC 的 id 和 index 分别是 2、1;此时新的 pointer 落下(可以是 pointerB 也可以不是),pointerA 的 id 和 index 分别是 0、0,pointerC 的 id 和 index 分别是 2、2,新的 pointer 的 id 和 index 分别是 1、1。

       id 的复用会导致 index 的重新调整,id 不会变化,而 index 在 pointer 抬起和落下时会变化,并且它永远是连续的。

多点触控的三种类型

接力型

       同⼀时刻只有⼀个 pointer 起作⽤,即最新的 pointer。

  • 典型: ListView、RecyclerView。
  • 实现方式:在 ACTION_POINTER_DOWN 和 ACTION_POINTER_UP 时记录下最新的 pointer,在之后的 ACTION_MOVE 事件中使⽤这个 pointer 来判断位置。
  • 代码详见 MultiTouchView1

配合型 / 协作型

       所有触摸到 View 的 pointer 共同起作⽤。

  • 典型: ScaleGestureDetector,以及 GestureDetector 的 onScroll() ⽅法判断。
  • 实现方式:在每个 DOWN、 POINTER_DOWN、 POINTER_UP、 UP 事件中使⽤所有 pointer 的坐标来共同更新焦点坐标,并在 MOVE 事件中使⽤所有 pointer 的坐标来判断位置。
  • 代码详见 MultiTouchView2

各⾃为战型

       各个 pointer 做不同的事,互不影响。

  • 典型:⽀持多画笔的画板应⽤。
  • 实现⽅式:在每个 DOWN、 POINTER_DOWN 事件中记录下每个 pointer 的 id,在 MOVE 事件中使⽤ id 对它们进⾏跟踪。
  • 代码详见 MultiTouchView3

参考资料:
腾讯课堂 HenCoder

Fork me on GitHub