Android自定义系列——14.MotionEvent

MotionEvent在android的触摸事件中起到了很重要的作用,本文主要介绍MotionEvent,简要介绍触摸事件,主要包括 单点触控、多点触控、鼠标事件 以及 getAction() 和 getActionMasked() 的区别。

Android 将所有的输入事件都放在了 MotionEvent 中:

版本号更新内容
Android 1.0 (API 1 )支持单点触控和轨迹球的事件。
Android 1.6 (API 4 )支持手势。
Android 2.0 (API 5 )支持多点触控。
Android 3.1 (API 12)支持触控笔,鼠标,键盘,操纵杆,游戏控制器等输入工具。

单点触控

事件简介
ACTION_DOWN手指 初次接触到屏幕 时触发。
ACTION_MOVE手指 在屏幕上滑动 时触发,会多次触发。
ACTION_UP手指 离开屏幕 时触发。
ACTION_CANCEL事件 被上层拦截 时触发。
ACTION_OUTSIDE手指 不在控件区域 时触发。

和以下的几个方法:

方法简介
getAction()获取事件类型。
getX()获得触摸点在当前 View 的 X 轴坐标。
getY()获得触摸点在当前 View 的 Y 轴坐标。
getRawX()获得触摸点在整个屏幕的 X 轴坐标。
getRawY()获得触摸点在整个屏幕的 Y 轴坐标。

手指落下(ACTION_DOWN) -> 多次移动(ACTION_MOVE) -> 离开(ACTION_UP)
本次事例中 ACTION_MOVE 有多次触发。
如果仅仅是单击(手指按下再抬起),不会触发 ACTION_MOVE。

针对单点触控的事件处理一般是这样写的:

@Override
public boolean onTouchEvent(MotionEvent event) {
    // ▼ 注意这里使用的是 getAction(),先埋一个小尾巴。
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
        	// 手指按下
            break;
        case MotionEvent.ACTION_MOVE:
            // 手指移动
            break;
        case MotionEvent.ACTION_UP:
            // 手指抬起
            break;
        case MotionEvent.ACTION_CANCEL:
            // 事件被拦截 
            break;
        case MotionEvent.ACTION_OUTSIDE:
            // 超出区域 
            break;
    }
    return super.onTouchEvent(event);
}

但其中有两个比较特殊的事件: ACTION_CANCEL 和 ACTION_OUTSIDE 。
为什么说特殊呢,因为它们是由程序触发而产生的,而且触发条件也非常特殊,通常情况下即便不处理这两个事件也没有什么问题。接下来我们就扒一扒它们的真面目:

ACTION_CANCEL

ACTION_CANCEL 的触发条件是事件被上层拦截,然而我们在 事件分发机制中了解到当事件被上层 View 拦截的时候,ChildView 是收不到任何事件的,ChildView 收不到任何事件,自然也不会收到 ACTION_CANCEL 了,所以说这个 ACTION_CANCEL 的正确触发条件并不是这样,那么是什么呢?

事实上,只有上层 View 回收事件处理权的时候,ChildView 才会收到一个 ACTION_CANCEL 事件。

例如:上层 View 是一个 RecyclerView,它收到了一个 ACTION_DOWN 事件,由于这个可能是点击事件,所以它先传递给对应 ItemView,询问 ItemView 是否需要这个事件,然而接下来又传递过来了一个 ACTION_MOVE 事件,且移动的方向和 RecyclerView 的可滑动方向一致,所以 RecyclerView 判断这个事件是滚动事件,于是要收回事件处理权,这时候对应的 ItemView 会收到一个 ACTION_CANCEL ,并且不会再收到后续事件。

ACTION_OUTSIDE

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

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

参见StackOverflow:https://stackoverflow.com/questions/8384067/how-to-dismiss-the-dialog-with-click-on-outside-of-the-dialog

多点触控

多个手指同时按在屏幕上,会产生很多的事件,这些事件该如何区分呢?
Android 在 2.0 版本的时候开始支持多点触控,一旦出现了多点触控,很多东西就突然之间变得麻烦起来了,首先要解决的问题就是 多个手指同时按在屏幕上,会产生很多的事件,这些事件该如何区分呢?

为了区分这些事件,工程师们用了一个很简单的办法--编号,当手指第一次按下时产生一个唯一的号码,手指抬起或者事件被拦截就回收编号,就这么简单。

第一次按下的手指特殊处理作为主指针,之后按下的手指作为辅助指针,然后随之衍生出来了以下事件(注意增加的事件和事件简介的变化):

事件简介
ACTION_DOWN第一个 手指 初次接触到屏幕 时触发。
ACTION_MOVE手指 在屏幕上滑动 时触发,会多次触发。
ACTION_UP最后一个 手指 离开屏幕 时触发。
ACTION_POINTER_DOWN有非主要的手指按下(即按下之前已经有手指在屏幕上)。
ACTION_POINTER_UP有非主要的手指抬起(即抬起之后仍然有手指在屏幕上)。

和以下方法:

方法简介
getActionMasked()与 getAction() 类似,多点触控必须使用这个方法获取事件类型。
getActionIndex()获取该事件是哪个指针(手指)产生的。
getPointerCount()获取在屏幕上手指的个数。
getPointerId(int pointerIndex)获取一个指针(手指)的唯一标识符ID,在手指按下和抬起之间ID始终不变。
findPointerIndex(int pointerId)通过PointerId获取到当前状态下PointIndex,之后通过PointIndex获取其他内容。
getX(int pointerIndex)获取某一个指针(手指)的X坐标
getY(int pointerIndex)获取某一个指针(手指)的Y坐标

getAction() 与 getActionMasked()
当多个手指在屏幕上按下的时候,会产生大量的事件
一般来说我们可以通过为事件添加一个int类型的index属性来区分,为了添加一个通常数值不会超过10的index属性就浪费一个int大小的空间简直是不能忍受的,于是工程师们将这个index属性和事件类型直接合并了。

int类型共32位(0x00000000),他们用最低8位(0x000000ff)表示事件类型,再往前的8位(0x0000ff00)表示事件编号,以手指按下为例讲解数值是如何合成的:

ACTION_DOWN 的默认数值为 (0x00000000)
ACTION_POINTER_DOWN 的默认数值为 (0x00000005)
手指按下触发事件(数值)
第1个手指按下ACTION_DOWN (0x00000000)
第2个手指按下ACTION_DOWN (0x00000000)
第3个手指按下ACTION_POINTER_DOWN (0x00000205)
第4个手指按下ACTION_POINTER_DOWN (0x00000305)

上面表格中用粗体标示出的数值,可以看到随着按下手指数量的增加,这个数值也是一直变化的,进而导致我们使用 getAction() 获取到的数值无法与标准的事件类型进行对比,为了解决这个问题,他们创建了一个 getActionMasked() 方法,这个方法可以清除index数值,让其变成一个标准的事件类型。

1、多点触控时必须使用 getActionMasked() 来获取事件类型。
2、单点触控时由于事件数值不变,使用 getAction() 和 getActionMasked() 两个方法都可以。
3、使用 getActionIndex() 可以获取到这个index数值。不过请注意,getActionIndex() 只在 down 和 up 时有效,move 时是无效的。

目前来说获取事件类型使用 getActionMasked()

PointId

虽然前面刚刚说了一个 actionIndex,可以使用 getActionIndex() 获得,但通过 actionIndex 字面意思知道,这个只表示事件的序号,而且根据其说明文档解释,这个 ActionIndex 只有在手指按下(down)和抬起(up)时是有用的,在移动(move)时是没有用的,事件追踪非常重要的一环就是移动(move)

追踪事件流,请认准 PointId,不能通过ActionIndex

PointId 在手指按下时产生,手指抬起或者事件被取消后消失,是一个事件流程中唯一不变的标识,可以在手指按下时 通过 getPointerId(int pointerIndex) 获得。 (参数中的 pointerIndex 就是 actionIndex)

获取压力(接触面积大小)

MotionEvent支持获取某些输入设备(手指或触控笔)的与屏幕的接触面积和压力大小,主要有以下方法:
描述中使用了手指,触控笔也是一样的。

方法简介
getSize ()获取第1个手指与屏幕接触面积的大小
getHistoricalSize (int pos)获取历史数据中第1个手指在第pos次事件中的接触面积
getSize (int pin)获取第pin个手指与屏幕接触面积的大小
getHistoricalSize (int pin, int pos)获取历史数据中第pin个手指在第pos次事件中的接触面积
getPressure ()获取第一个手指的压力大小
getPressure (int pin)获取第pin个手指的压力大小
getHistoricalPressure (int pos)获取历史数据中第1个手指在第pos次事件中的压力大小
getHistoricalPressure (int pin, int pos)获取历史数据中第pin个手指在第pos次事件中的压力大小

pin 全称是 pointerIndex,表示第几个手指。(pin < getPointerCount() )
pos 表示历史数据中的第几个数据。( pos < getHistorySize() )

1、获取接触面积大小和获取压力大小是需要硬件支持的。
2、非常不幸的是大部分设备所使用的电容屏不支持压力检测,但能够大致检测出接触面积。
3、大部分设备的 getPressure() 是使用接触面积来模拟的。
4、由于某些未知的原因(可能系统版本和硬件问题),某些设备不支持该方法。

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

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

鼠标事件

讲解一下与鼠标相关的几个事件:

事件简介
ACTION_HOVER_ENTER指针移入到窗口或者View区域,但没有按下。
ACTION_HOVER_MOVE指针在窗口或者View区域移动,但没有按下。
ACTION_HOVER_EXIT指针移出到窗口或者View区域,但没有按下。
ACTION_SCROLL滚轮滚动,可以触发水平滚动(AXIS_HSCROLL)或者垂直滚动(AXIS_VSCROLL)

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

输入设备类型判断

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

设备类型简介
TOOL_TYPE_ERASER橡皮擦
TOOL_TYPE_FINGER手指
TOOL_TYPE_MOUSE鼠标
TOOL_TYPE_STYLUS手写笔
TOOL_TYPE_UNKNOWN未知类型

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的代码家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值