Android MotionEvent 详解,之前用了两篇文章 事件分发机制原理 和 事件分发机制详解 来讲解事件分发,而作为事件分发主角之一的 MotionEvent 并没有过多的说明,本文就带大家了解 MotionEvent 的相关内容,简要介绍触摸事件,主要包括 单点触控、多点触控、鼠标事件 以及 getAction() 和 getActionMasked() 的区别。
Android 将所有的输入事件都放在了 MotionEvent 中,随着安卓的不断发展壮大,MotionEvent 也开始变得越来越复杂,下面是我自己整理的 MotionEvent 大事记:
版本号 | 更新内容 |
---|---|
Android 1.0 (API 1 ) | 支持单点触控和轨迹球的事件。 |
Android 1.6 (API 4 ) | 支持手势。 |
Android 2.0 (API 5 ) | 支持多点触控。 |
Android 3.1 (API 12) | 支持触控笔,鼠标,键盘,操纵杆,游戏控制器等输入工具。 |
以上仅仅是简要的说明几次比较大的变动,细小的修复和更新不计其数,此处就不一一列出了,反正也没人关心这些东西。 MotionEvent 负责集中处理所有类型设备的输入事件,但是由于某些设备使用的几率较小本文会忽略讲解,或者简要讲解,例如: 1、轨迹球只出现在最早的设备上,现代的设备上已经见不到了,本文不再叙述。 2、触控笔和手指处理流程基本相同,不再多说。 3、鼠标在手机上使用概率也比较小,会在文末简要介绍。
单点触控
单点触控就非常简单啦,入门的工程师都会用,上一篇文章也简要介绍过,主要涉及以下几个事件:
事件 | 简介 |
---|---|
ACTION_DOWN | 手指 初次接触到屏幕 时触发。 |
ACTION_MOVE | 手指 在屏幕上滑动 时触发,会多次触发。 |
ACTION_UP | 手指 离开屏幕 时触发。 |
ACTION_CANCEL | 事件 被上层拦截 时触发。 |
ACTION_OUTSIDE | 手指 不在控件区域 时触发。 |
和以下的几个方法:
方法 | 简介 |
---|---|
getAction() | 获取事件类型。 |
getX() | 获得触摸点在当前 View 的 X 轴坐标。 |
getY() | 获得触摸点在当前 View 的 Y 轴坐标。 |
getRawX() | 获得触摸点在整个屏幕的 X 轴坐标。 |
getRawY() | 获得触摸点在整个屏幕的 Y 轴坐标。 |
关于
get
和getRaw
的区别可以参考这一篇文章 安卓自定义View基础-坐标系
单点触控一次简单的交互流程是这样的:
手指落下(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
,并且不会再收到后续事件。通俗一点?
RecyclerView:儿砸,这里有一个
ACTION_DOWN
你看你要不要。 ItemView :好嘞,我看看。 RecyclerView:噫?居然是移动事件ACTION_MOVE
,我要滚起来了,儿砸,我可能要把你送去你姑父家(缓存区)了,在这之前给你一个ACTION_CANCEL
,你要收好啊。 ItemView :……这是实际开发中最有可能见到
ACTION_CANCEL
的场景了。
ACTION_OUTSIDE
ACTION_OUTSIDE
的触发条件更加奇葩,从字面上看,outside 意思不就是超出区域么?然而不论你如何滑动超出控件区域都不会触发 ACTION_OUTSIDE
这个事件。相信很多魔法师都对此很是疑惑,说好的超出区域呢?
实际上这个事件根本就不是在这里用的,看官方解释(装一下逼):
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
](developer.android.com/reference/a…
续集:
- Android自定义控件进阶01-自定义控件开发套路与流程
- Android自定义控件进阶02-Canvas之绘制图形
- Android自定义控件进阶03-Canvas之画布操作
- Android自定义控件进阶04-Canvas之图片文字
- Android自定义控件进阶05-Path之基本操作
- Android自定义控件进阶06-Path之贝塞尔曲线
- Android自定义控件进阶07-Path之完结篇
- Android自定义控件进阶08-PathMeasure详解
- Android自定义控件进阶09-控件核心Matrix原理
- Android自定义控件进阶10-控件核心Matrix Camera
- Android自定义控件进阶11-事件分发机制原理
- Android自定义控件进阶11-事件分发机制原理01
- Android自定义控件进阶12-事件分发机制原理02
- Android自定义控件进阶13-MotionEvent详解
- Android自定义控件进阶14-特殊控件的事件处理方案