手势识别Android

Android触摸事件的传递(一)--MotionEvent

2018-07-20  本文已影响0人  凯玲之恋

了解更多,移步Android触摸事件传递机制系列详解

1. MotionEvent 简介

以上仅仅是简要的说明几次比较大的变动
MotionEvent 负责集中处理所有类型设备的输入事件,但是由于某些设备使用的几率较小本文会忽略讲解,或者简要讲解,例如:

  1. 轨迹球只出现在最早的设备上,现代的设备上已经见不到了,本文不再叙述。
  2. 触控笔和手指处理流程基本相同,不再多说。
  3. 鼠标在手机上使用概率也比较小,会在文末简要介绍。

1.1 事件类型

涉及MotionEvent的事件类型主要有:

    public static final int ACTION_DOWN             = 0;
    public static final int ACTION_UP               = 1;
    public static final int ACTION_MOVE             = 2;
    public static final int ACTION_CANCEL           = 3;
    public static final int ACTION_OUTSIDE          = 4;
    public static final int ACTION_POINTER_DOWN     = 5;
    public static final int ACTION_POINTER_UP       = 6;
  1. ACTION_DOWN: 第一个手指按下时
  2. ACTION_MOVE:按住一点在屏幕上移动
  3. ACTION_UP:最后一个手指抬起时
  4. ACTION_CANCEL:当前的手势被取消了,并且再也不会接收到后续的触摸事件,这时我们就像ACTION_UP一样对待他以结束该手势操作,但是却不执行我们在ACTION_UP时需要执行的动作。
    ViewGroup分发事件的机制。一般来说,如果一个子视图接收了父视图分发给它的ACTION_DOWN事件,那么与ACTION_DOWN事件相关的事件流就都要分发给这个子视图,但是如果父视图希望拦截其中的一些事件,不再继续转发事件给这个子视图的话,那么就需要给子视图一个ACTION_CANCEL事件。这在后续文章中源码分析部分也有体现。
  5. ACTION_OUTSIDE: 表示用户触碰超出了正常的UI边界.

A movement has happened outside of the normal bounds of the UI element. This does not provide a full gesture, but only the initial location of the movement/touch.
一个触摸事件已经发生了UI元素的正常范围之外。因此不再提供完整的手势,只提供 运动/触摸 的初始位置。

我们知道,正常情况下,如果初始点击位置在该视图区域之外,该视图根本不可能会收到事件,然而,万事万物都不是绝对的,肯定还有一些特殊情况,你可曾还记得点击 Dialog 区域外关闭吗?Dialog就是一个特殊的视图(没有占满屏幕大小的窗口),能够接收到视图区域外的事件(虽然在通常情况下你根本用不到这个事件),除了 Dialog之外,你最可能看到这个事件的场景是悬浮窗,当然啦,想要接收到视图之外的事件需要一些特殊的设置。

设置视图的 WindowManager 布局参数的 flags为FLAG_WATCH_OUTSIDE_TOUCH ,这样点击事件发生在这个视图之外时,该视图就可以接收到一个 ACTION_OUTSIDE
事件。

  1. ACTION_POINTER_DOWN:代表用户又使用一个手指触摸到屏幕上,也就是说,在已经有一个触摸点的情况下,又新出现了一个触摸点。
  2. ACTION_POINTER_UP::代表用户的一个手指离开了触摸屏,但是还有其他手指还在触摸屏上。也就是说,在多个触摸点存在的情况下,其中一个触摸点消失了。它与ACTION_UP的区别就是,它是在多个触摸点中的一个触摸点消失时(此时,还有触摸点存在,也就是说用户还有手指触摸屏幕)产生,而8. ACTION_UP可以说是最后一个触摸点消失时产生。会在多指触摸和Pointers章节详解。

1.2 坐标

event.getX(); //触摸点相对于View左上角为原点坐标系的X坐标
event.getY(); //触摸点相对于View左上角为原点坐标系的Y坐标
event.getRawX(); //触摸点相对于屏幕左上角为原点坐标系的X坐标
event.getRawY(); //触摸点相对于屏幕左上角为原点坐标系的Y坐标

2 单点触控

主要涉及以下几个事件:

触摸手势:0--ACTION_DOWN
触摸手势:1--ACTION_UP

点击按下后滑动一下再抬起

com.zlq.customwidget V/TouchEventTest: 触摸手势:0--ACTION_DOWN
com.zlq.customwidget V/TouchEventTest: 触摸手势:2--ACTION_MOVE
com.zlq.customwidget V/TouchEventTest: 触摸手势:2--ACTION_MOVE
...
com.zlq.customwidget V/TouchEventTest: 触摸手势:1--ACTION_UP

3 多点触控

4 getAction()getActionMasked()

ACTION_DOWN 的默认数值为 (0x00000000)
ACTION_POINTER_DOWN 的默认数值为 (0x00000005)

5527952-1ae9b16b46f6c5d1.png
注意:
  1. 多点触控时必须使用 getActionMasked() 来获取事件类型。
  2. 单点触控时由于事件数值不变,使用 getAction()getActionMasked()两个方法都可以。
  3. 使用 getActionIndex() 可以获取到这个index数值。不过请注意,getActionIndex()只在downup 时有效,move时是无效的。
    目前来说获取事件类型使用 getActionMasked() 就行了,但是如果一定要编译时兼容古董版本的话,可以考虑使用这样的写法:
final int action = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO)
                ? event.getActionMasked()
                : event.getAction();
switch (action){
    case MotionEvent.ACTION_DOWN:
        // TODO
        break;
}

5 PointId

6 历史数据(批处理)

  1. 历史数据只有 ACTION_MOVE 事件。
  2. 历史数据单点触控和多点触控均可以用。
    下面是官方文档给出的一个简单使用示例:
void printSamples(MotionEvent ev) {
     final int historySize = ev.getHistorySize();
     final int pointerCount = ev.getPointerCount();
     for (int h = 0; h < historySize; h++) {
         System.out.printf("At time %d:", ev.getHistoricalEventTime(h));
         for (int p = 0; p < pointerCount; p++) {
             System.out.printf("  pointer %d: (%f,%f)",
                 ev.getPointerId(p), ev.getHistoricalX(p, h), ev.getHistoricalY(p, h));
         }
     }
     System.out.printf("At time %d:", ev.getEventTime());
     for (int p = 0; p < pointerCount; p++) {
         System.out.printf("  pointer %d: (%f,%f)",
             ev.getPointerId(p), ev.getX(p), ev.getY(p));
     }
}

7 获取事件发生的时间

5527952-ab6fb1455ee9ffb4.png
  1. pos 表示历史数据中的第几个数据。( pos < getHistorySize() )
  2. 返回值类型为 long,单位是毫秒。

获取压力(接触面积大小)
MotionEvent支持获取某些输入设备(手指或触控笔)的与屏幕的接触面积和压力大小,主要有以下方法:

5527952-ac693b084dc2a9e4.png
  1. pin全称是pointerIndex,表示第几个手指。(pin < getPointerCount() )
  2. pos表示历史数据中的第几个数据。( pos < getHistorySize() )
    注意:
  1. 获取接触面积大小和获取压力大小是需要硬件支持的。
  2. 非常不幸的是大部分设备所使用的电容屏不支持压力检测,但能够大致检测出接触面积。
  3. 大部分设备的 getPressure() 是使用接触面积来模拟的。
  4. 由于某些未知的原因(可能系统版本和硬件问题),某些设备不支持该方法。

我用不同的设备对这两个方法进行了测试,然而不同设备测试出来的结果不相同,之后经过我多方查证,发现是系统问题,有的设备上只有 getSize() 能用,有的设备上只有 getPressure() 能用,而有的则两个都不能用。

由于获取接触面积和获取压力大小受系统和硬件影响,使用的时候一定要进行数据检测,以防因为设备问题而导致程序出错。

8 鼠标事件

由于触控笔事件和手指事件处理流程大致相同,所以就不讲解了,这里讲解一下与鼠标相关的几个事件:


5527952-da662a3bcd2fe073.png

1、这些事件类型是 安卓4.0 (API 14) 才添加的。2、使用 getActionMasked() 获得这些事件类型。3、这些事件不会传递到 onTouchEvent(MotionEvent) 而是传递到 onGenericMotionEvent(MotionEvent)

9 输入设备类型判断

输入设备类型判断也是安卓4.0 (API 14) 才添加的,主要包括以下几种设备:

5527952-983a57cf78336d1d.png
使用 getToolType(int pointerIndex) 来获取对应的输入设备类型,pointIndex可以为0,但必须小于 getPointerCount()

参考

自定义View进阶《十四》——MotionEvent详解
Android触摸事件--MotionEvent

上一篇 下一篇

猜你喜欢

热点阅读