前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android自定义系列——14.MotionEvent

Android自定义系列——14.MotionEvent

作者头像
老马的编程之旅
发布2022-06-22 13:15:45
1.8K0
发布2022-06-22 13:15:45
举报
文章被收录于专栏:深入理解Android

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。

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

代码语言:javascript
复制
@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)表示事件编号,以手指按下为例讲解数值是如何合成的:

代码语言:javascript
复制
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()。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单点触控
  • ACTION_CANCEL
  • ACTION_OUTSIDE
  • 多点触控
  • PointId
  • 获取压力(接触面积大小)
  • 鼠标事件
  • 输入设备类型判断
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档