Android MotionEvent

article/2025/9/14 14:28:44

1.MotionEvent
Android将所有的输入事件都放在了 MotionEvent 中,MotionEvent 负责集中处理所有类型设备的输入事件,包括单点触控、手势、多点触控、触控笔、鼠标、键盘、操纵杆、游戏控制器等。

事件类型:
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;

①ACTION_DOWN:第一个手指按下时
②ACTION_MOVE:按住一点在屏幕上移动
③ACTION_UP:最后一个手指抬起时
④ACTION_CANCEL:不是由用户直接触发,而是由系统在需要的时候触发,例如子view接收到了down事件,但是在move的时候,父view通过onInterceptTouchEvent()返回true,拦截了该事件流,从子view拿回了处理事件的控制权,这时候子view就会收到一个ACTION_CANCEL事件,并且子view再也不会收到后续事件了,后续事件都交给父view来处理了。
简单来说,根据ViewGroup分发事件的机制,一般来说,如果一个子视图接收了父视图分发给它的ACTION_DOWN事件,那么与ACTION_DOWN事件相关的事件流就都要分发给这个子视图,但是如果父视图希望拦截其中的一些事件,不再继续转发事件给这个子视图的话,那么就需要给子视图一个ACTION_CANCEL事件。
⑤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就是一个特殊的视图(没有占满屏幕大小的窗口),能够接收到视图区域外的事件(虽然在通常情况下你根本用不到这个事件)。
当然,想要接收到视图之外的事件需要一些特殊的设置,即设置视图的 WindowManager 布局参数的 flags为FLAG_WATCH_OUTSIDE_TOUCH ,这样点击事件发生在这个视图之外时,该视图就可以接收到一个 ACTION_OUTSIDE事件。
⑥ACTION_POINTER_DOWN:用户又使用一个手指触摸到屏幕上,也就是说,在已经有一个触摸点的情况下,又新出现了一个触摸点。
⑦ACTION_POINTER_UP:用户的一个手指离开了触摸屏,但是还有其他手指还在触摸屏上。也就是说,在多个触摸点存在的情况下,其中一个触摸点消失了。
它与ACTION_UP的区别就是,它是在多个触摸点中的一个触摸点消失时产生,而ACTION_UP是最后一个触摸点消失时产生。
⑧MotionEvent.ACTION_SCROLL:android3.1引入,非触摸滚动,主要是由鼠标、滚轮、轨迹球触发。

常用方法:
①getAction():返回动作类型
②getX()/getY():事件发生时,触摸的中间区域的X/Y坐标,由这两个函数获得的X/Y值是相对坐标,相对于消费这个事件的视图的左上角的坐标。
③getRawX()/getRawY():由这两个函数获得的X/Y值是绝对坐标,是相对于屏幕的。
在这里插入图片描述
④getSize():指压范围,获取第一个手指与屏幕接触面积的大小
getSize(int pointerIndex):获取第pointerIndex个手指与屏幕接触面积的大小
⑤getPressure():获取第一个手指的压力大小,0-1之间,看情况,很可能始终返回1,具体的级别由驱动和物理硬件决定的
getPressure(int pointerIndex):获取第pointerIndex个手指的压力大小
⑥getEdgeFlags():当事件类型是ActionDown时可以通过此方法获得边缘标记(EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM),但是看设备情况,很可能始终返回0
⑦getDownTime() :按下开始时间
⑧getEventTime() : 事件结束时间
⑨getActionMasked():多点触控获取经过掩码后的动作类型
⑩getActionIndex():多点触控获取经过掩码和平移后的索引
⑩getPointerCount():获取触控点的数量,比如2则可能是两个手指同时按压屏幕
⑩getPointerId(int pointerIndex):获取一个指针(手指)的唯一标识符ID,在手指按下和抬起之间ID始终不变。
⑩findPointerIndex(int pointerId):通过pointerId获取到当前状态下PointerIndex,之后可以通过PointerIndex获取其他内容。
⑩getX(int pointerIndex):获取第PointerIndex个触控点的x位置
⑩getY(int PointerIndex):获取第PointerIndex个触控点的y位置
⑩getPressure(nID):获取第nID个触控点的压力

动作类型和动作索引:
单点触控时用8位二进制数代表动作类型,如0x01,这时getAction返回的值就是ACTION_UP。
多点触控时因为增加了本次触摸的索引,所以改用16位二进制数,如0x0001,低8位代表动作的类型,高8位代表索引。这时获取动作类型就需要用掩码盖掉高8位,而获取索引需要用掩码盖掉低8位然后再右移8位,如下:
public static final int ACTION_MASK = 0xff;
public static final int ACTION_POINTER_INDEX_MASK = 0xff00;
public static final int ACTION_POINTER_INDEX_SHIFT = 8;
public final int getActionMasked() {
return mAction & ACTION_MASK;
}
public final int getActionIndex() {
return (mAction & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
}

2.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_POINTER_DOWN (0x00000105);
第3个手指按下,触发事件ACTION_POINTER_DOWN (0x00000205);
第4个手指按下,触发事件ACTION_POINTER_DOWN (0x00000305);

注意:
可以看到随着按下手指数量的增加,这个数值的高8位也是一直变化的,进而导致我们使用 getAction() 获取到的数值无法与标准的事件类型进行对比。为了解决这个问题,他们创建了一个 getActionMasked() 方法,这个方法可以清除index数值,让其变成一个标准的事件类型。
①多点触控时必须使用 getActionMasked() 来获取事件类型。
②单点触控时由于事件数值不变,使用 getAction() 和 getActionMasked() 两个方法都可以。
③使用 getActionIndex() 可以获取到这个index数值。不过要注意,getActionIndex() 只在 down 和 up 时有效,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;
}

3.Pointer
当用户两个或者多个手指在屏幕上滑动时,为了可以表示多个触摸点的动作,MotionEvent中引入了Pointer的概念,一个pointer就代表一个触摸点,每个pointer都有自己的事件类型,也有自己的横轴坐标值。一个MotionEvent对象中可能会存储多个pointer的相关信息,每个pointer都会有一个自己的id和index。pointer的id在整个事件流中是不会发生变化的,但是index会发生变化。

MotionEvent类中的很多方法都是可以传入一个int值作为参数的,其实传入的就是pointer的index值。比如getX(pointerIndex)和getY(pointerIndex),此时,它们返回的就是index所代表的触摸点相关事件坐标值。
由于pointer的index值在不同的MotionEvent对象中会发生变化,但是id值却不会变化。所以,当我们要记录一个触摸点的事件流时,就只需要保存其id,然后使用findPointerIndex(int)来获得其index值,然后再获得其他信息。

PointId:
追踪事件流,一定要用 PointId,这是唯一官方指定标准,不要使用 ActionIndex 。
PointId 在手指按下时产生,手指抬起或者事件被取消后消失,是一个事件流程中唯一不变的标识,可以在手指按下时,通过 getPointerId(int pointerIndex) 获得 (参数中的 pointerIndex 就是 actionIndex)。
在一次运动中,触点出现的顺序是不确定的,因此,触点的索引值会由于事件的变化而变化,但是只要触点处于活动状态,该触点的id就不会改变。用getPointId(int)方法可以得到触点的id值,从而根据得到的id值在一连串的动作中来追踪其运动轨迹。然而在连续的运动事件中,应该用findPointIndex(int)方法通过触点的id值得到触点的索引值。
private final static int INVALID_ID = -1;
private int mActivePointerId = INVALID_ID;
private int mSecondaryPointerId = INVALID_ID;
private float mPrimaryLastX = -1;
private float mPrimaryLastY = -1;
private float mSecondaryLastX = -1;
private float mSecondaryLastY = -1;
public boolean onTouchEvent(MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case MotionEvent.ACTION_DOWN:
int index = event.getActionIndex();
mActivePointerId = event.getPointerId(index);
mPrimaryLastX = MotionEventCompat.getX(event,index);
mPrimaryLastY = MotionEventCompat.getY(event,index);
break;
case MotionEvent.ACTION_POINTER_DOWN:
index = event.getActionIndex();
mSecondaryPointerId = event.getPointerId(index);
mSecondaryLastX = event.getX(index);
mSecondaryLastY = event.getY(index);
break;
case MotionEvent.ACTION_MOVE:
index = event.findPointerIndex(mActivePointerId);
int secondaryIndex = MotionEventCompat.findPointerIndex(event,mSecondaryPointerId);
final float x = MotionEventCompat.getX(event,index);
final float y = MotionEventCompat.getY(event,index);
final float secondX = MotionEventCompat.getX(event,secondaryIndex);
final float secondY = MotionEventCompat.getY(event,secondaryIndex);
break;
case MotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mActivePointerId = INVALID_ID;
mPrimaryLastX =-1;
mPrimaryLastY = -1;
break;
}
return true;
}

4.MotionEvent回收
MotionEvent类中有个recycle()方法,但是不要通过此方法对MotionEvent对象进行回收。如果回调方法没有使用MotionEvent对象,并且返回了false,MotionEvent对象可能会被传递到其他某个方法、视图或我们的活动。
MotionEvent类中还有和obtain()方法,通过此方法可以创建一个MotionEvent的副本或者全新的MotionEvent,这个副本或全新的事件对象是在完成之后应该回收的对象。


http://chatgpt.dhexx.cn/article/wSKjjSn4.shtml

相关文章

[Android]视图的控触操作-MotionEvent

引入 对屏幕的任何操作,系统都会创建一个触摸事件的对象MotionEvent来应对这个操作。当点击手机屏幕的某一个视图时,最先感应到的是屏幕,因为Activity系统是分层的结构,底层是一些驱动,所以驱动就会得到信息并且把信息…

MotionEvent 详解

Android MotionEvent详解: https://www.jianshu.com/p/0c863bbde8eb https://www.diycode.cc/topics/392 Android 将所有的输入事件都放在了 MotionEvent 中,随着安卓的不断发展壮大,MotionEvent 也开始变得越来越复杂,下面是我自己整理的 …

MotionLayout MotionScene 动画从未如此简单!

话不多说先上图。 这是要做的最终效果。通过这些动画我们将了解MotionLayout的使用方法和常用的一些属性。 第一步:添加依赖 如果要使用MotionLayout请将ConstraintLayout更新到2.0及以上。在build.gradle文件中添加依赖 如果使用的是AndroidX,添加依赖…

Android的MotionEvent和事件处理

之前几篇文章我们讲解了自定义View和ViewGroup, 今天我们来看下View和ViewGroup常见的触摸事件和按键事件。 MotionEvent MotionEvent对象是与用户触摸相关的时间序列,该序列从用户首次触摸屏幕开始,经历手指在屏幕表面的任何移动,直到手指…

MotionEvent详解

Android MotionEvent 详解,之前用了两篇文章 事件分发机制原理 和 事件分发机制详解 来讲解事件分发,而作为事件分发主角之一的 MotionEvent 并没有过多的说明,本文就带大家了解 MotionEvent 的相关内容,简要介绍触摸事件&#xf…

Android自定义View进阶-MotionEvent详解

欢迎Follow我的GitHub, 关注我的CSDN. 其余参考Android目录 我们微信公众号:杨守乐 推荐文章: 如果你喜欢上了一个程序员小伙,献给所有的程序员女友 学习资料(干货汇集)不断更新【更新于2017-2-25】 Android Studi…

常用awk命令整理

AWK倾向于一行一行的数据进行处理 awk 用法:awk pattern {action} 变量名 含义 ARGC 命令行变元个数 ARGV 命令行变元数组 FILENAME 当前输入文件名 FNR 当前文件中的记录号 FS 输入域分隔符,默认为一个空格 RS 输入记录分隔符 NF 当…

sed命令和awk命令

sed命令 sed命令■什么是sed■sed的工作流程■命令格式■常用选项:■常用操作:■打印内容■sed编辑器的寻址方式■删除行■替换■插入 awk命令■awk命令的工作原理■命令格式■常见的内建变量■按行输出文本■按字段输出文本:■通过管道、双引号调用Shell 命令■拓展 sed命令 …

awk命令应用

记录:353 场景:在CentOS 7.9操作系统上,使用awk文本处理工具处理文本;使用awk、cat和grep搭配使用处理文本;使用awk直接处理文本;使用shell脚本调用awk脚本处理文本。 版本: 操作系统&#x…

概要设计之功能模块

功能模块描述 所谓功能模块,从字面上理解,就是以功能来进行划分模块。 接着,根据功能特性多少,决定是否要划分“子功能模块”。 这里就容易出现一个问题,如何去确定每个功能的界限呢,以及很多人会拿用户角…

系统架构图编写(概要设计)

系统架构图编写(概要设计) 应用架构图、技术架构图、业务架构图定义以及到底怎么画好架构图? 常见的数据库架构设计方案? 业务架构的定义、特性和方法 架构图之间的关系 业务架构图 业务架构,是IT架构的基础。 是从业…

概要设计、详细设计:概念、方法、实践步骤

完整软件开发流程: 需求分析、概要设计、详细设计 一 1. 概念、方法、实践步骤 设计是指根据需求开发的结果,对产品的技术实现由粗到细进行设计的过程。根据设计粒度和目的的不同可以将设计分为概要设计、详细设计等阶段以便于管理和确保质量。设计内容…

概要设计与详细设计如何编写

撰写的设计文档主要分为:总体概要设计文档 详细设计文档,后简称为“概设”“详设”。 总设和详设都应该包含的部分: (1) 需求:一般以产品的语言描述,这一块可以拷贝产品需求文档中的story li…

软件项目总体设计

软件项目总体设计 目录 1.导言 1 1.1目的 1 1.2范围 1 1.3参考资料 2 2.项目设计原则简介 2 3.功能模块设计 2 3.1功能模块设计总述 2 3.2 客户端子系统模块设计 4 3.2.1 模块 CM1 :静态页面 4 3.2.2 模块 CM2:系统登录 5 3.2.3 模块 CM3 :注…

软件概要设计的过程与任务

在完成对软件系统的需求分析之后,接下来需要进行的是软件系统的概要设计。一般说来,对于较大规模的软件项目,软件设计往往被分成两个阶段进行。首先是前期概要设计,用于确定软件系统的基本框架;然后是在概要设计基础上…

软件工程技术--第四章 概要设计

第四章 概要设计 4.1 软件设计概述 4.1.1 软件设计的概念与重要性 ​ 软件设计是软件工程的重要阶段,是一个将软件需求转换为软件表示的过程。软件设计的基本目标是用比较抽象概括的方式确定目标系统如何完成预定的任务,即确定系统的物理模型&#xff0…

ios 新建项目关于Main.storyboard的处理

使用xcode新建新建项目时,都会带一个main.storyboard的主界面。如果你不进行代码控制,默认APP启动会加载main.storyboard这个界面。怎么使用这个界面来加载这里就不在介绍了,这里只说用代码加载主页,不使用main.storyboard时&…

storyboard 使用

Storyboard是一项令人兴奋的功能,在iOS5中首次推出,在开发app的界面时可以极大地节省时间。 如下图所示,这就是一个完整的应用的storyboard,接下来我们要学习如何通过这种方式创建应用。 现在你可能还不是很精确地知道我们的应用可…

ios storyboard简单用法

使用xcode5中的storyboard,做个简单的界面,第一次用简直一头雾水,摸索下来感觉也蛮方便的。 从左到右,从上到下,控件依次是:UITextFiled,UIButton, UILabel, UIPickerVi…

iOS开发18:Storyboard的简单使用

之前做的例子,我们经常会用到.xib文件,在其中我们可以进行界面的设计。不过如果想切换视图,我们就得自己写很多代码。自从苹果推出了Storyboard,我们可以在一个编辑区域设计多个视图,并通过可视化的方法进行各个视图之…