Android MotionEvent理解

article/2025/9/14 4:09:42

Java层MotionEvent对应C++层MotionEvent

  Java层MotionEvent类下面的mNativePtr指向C++层MotionEvent
  C++层MotionEvent成员变量如下

class MotionEvent : public InputEvent {
…………
protected:int32_t mAction; //事件的行为,例如Down,Move,Up,如果是ACTION_POINTER_DOWN事件,则包含着一个移位的pointer indexint32_t mActionButton; //用于行为是ACTION_BUTTON_PRESS或ACTION_BUTTON_RELEASE,表示哪个按键是被按下或者释放。如果不是这两个行为事件,该值未定义int32_t mFlags; // 移动事件的flags,int32_t mEdgeFlags; // 对于触摸事件,用来作为显示屏的边缘int32_t mMetaState; // meta key被按下的bit位int32_t mButtonState; // 表示被按下的按键的状态,例如鼠标或触控笔。因为被按下的按键都是使用bit位来表示的,所以返回的就是某个按键被按下了。MotionClassification mClassification;float mXScale;// X坐标值的缩放float mYScale;//  Y坐标值的缩放float mXOffset; //  X坐标值的偏移float mYOffset; //  Y坐标值的偏移float mXPrecision; //  X坐标值的精确度float mYPrecision; //  Y坐标值的偏移float mRawXCursorPosition;// 鼠标事件中鼠标在X坐标的位置值float mRawYCursorPosition;// 鼠标事件中鼠标在Y坐标的位置值nsecs_t mDownTime; // 事件按下的时间Vector<PointerProperties> mPointerProperties;Vector<nsecs_t> mSampleEventTimes;Vector<PointerCoords> mSamplePointerCoords;
}

  其中的成员变量mPointerProperties、mSampleEventTimes、mSamplePointerCoords都是向量类型,里面分别存放什么数据呢?
  手指快速滑动中,MotionEvent类中可以批次存放多个Sample数据。如果只有一个手指滑动,一个Sample数据只有一个数据,如果多个手指参与滑动,一个Sample数据里面的数据量就是手指的数量。在代码中一个手指对应一个Pointer。多个Sample数据分成历史Sample数据和当前数据。
  成员变量mSamplePointerCoords会存储这批历史数据和当前数据,如果存在历史数据,会在前面存放历史数据,最后面存放当前数据。前面的历史数据数量=历史Sample数量×Pointer数量(触摸事件则为手指数量),其中历史Sample数量=mSampleEventTimes.size() - 1, Pointer数量=mPointerProperties.size()。其中mSamplePointerCoords中的数据存放位置如下图
mSamplePointerCoords中的数据存放  mSampleEventTimes中存放的是每个sample对应的时间。

action的获取

final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

  int action = ev.getAction();
  进入Motion里面查看实现

    public final int getAction() {return nativeGetAction(mNativePtr);}

  C++中对应的方法android_view_MotionEvent_nativeGetAction,看下实现

static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);return event->getAction();
}
class MotionEvent : public InputEvent {
public:…………inline int32_t getAction() const { return mAction; }
}

  可见,通过取到成员变量mAction的值与MotionEvent.ACTION_MASK做&操作,MotionEvent.ACTION_MASK的值是0xff,所以action的值是存在成员变量mAction的低8位。

pointer index的获取

final int actionIndex = ev.getActionIndex();
    public final int getActionIndex() {return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)>> ACTION_POINTER_INDEX_SHIFT;}

  也是通过nativeGetAction方法先获得action的值,然后与ACTION_POINTER_INDEX_MASK做&操作,ACTION_POINTER_INDEX_MASK的值为0xff00,可以知道poniter index存在action的第二个字节内,取到值之后向右移动ACTION_POINTER_INDEX_SHIFT位,ACTION_POINTER_INDEX_SHIFT的值为8,所以就取到了第二个字节的值了。
现在总结一下,C++的MotionEvent类的成员变量mAction是一个4个字节的int32,第一个字节内存放action值,第二个字节内存放pointer index值。

  并且这个pointer index只在事件类型是ACTION_POINTER_DOWN和ACTION_POINTER_UP的时候存在成员变量mAction中。

pointer id的获取

    public final int getPointerId(int pointerIndex) {return nativeGetPointerId(mNativePtr, pointerIndex);}

  对应c++层的方法

static jint android_view_MotionEvent_nativeGetPointerId(JNIEnv* env, jclass clazz,jlong nativePtr, jint pointerIndex) {MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);size_t pointerCount = event->getPointerCount();if (!validatePointerIndex(env, pointerIndex, pointerCount)) {return -1;}return event->getPointerId(pointerIndex);
}

  找到对应的MotionEvent,然后验证下pointerIndex,再接着调用MotionEvent的getPointerId方法

    inline int32_t getPointerId(size_t pointerIndex) const {return mPointerProperties[pointerIndex].id;}

  得到的是成员变量向量mPointerProperties对应的pointerIndex下标的id值。mPointerProperties是Vector类型,看下PointerProperties数据结构

/** Pointer property data.*/
struct PointerProperties {// The id of the pointer.int32_t id;// The pointer tool type.int32_t toolType;inline void clear() {id = -1;toolType = 0;}bool operator==(const PointerProperties& other) const;inline bool operator!=(const PointerProperties& other) const {return !(*this == other);}void copyFrom(const PointerProperties& other);
};

  可见,里面存着id和toolType。
  关于pointer id需要知道的是,在一次事件中每个手指的id是不会变的,但是index是会变化的。例如刚开始A手指先按下,A的index=0,id=0,现在B手指也按下,B的index=1,id=1。后面A手指抬起了之后,B的index=0,id=1。所以在多点触控中,需要找到相同手指的属性信息,是通过id来确定手指的。

事件分解

  将原来的事件分解成含特定id的事件,代码如下:

    /*** Splits a motion event such that it includes only a subset of pointer ids.* @hide*/@UnsupportedAppUsagepublic final MotionEvent split(int idBits) {MotionEvent ev = obtain();synchronized (gSharedTempLock) {final int oldPointerCount = nativeGetPointerCount(mNativePtr);ensureSharedTempPointerCapacity(oldPointerCount);final PointerProperties[] pp = gSharedTempPointerProperties;final PointerCoords[] pc = gSharedTempPointerCoords;final int[] map = gSharedTempPointerIndexMap;final int oldAction = nativeGetAction(mNativePtr);final int oldActionMasked = oldAction & ACTION_MASK;final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)>> ACTION_POINTER_INDEX_SHIFT;int newActionPointerIndex = -1;int newPointerCount = 0;for (int i = 0; i < oldPointerCount; i++) {nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);final int idBit = 1 << pp[newPointerCount].id;if ((idBit & idBits) != 0) {if (i == oldActionPointerIndex) {newActionPointerIndex = newPointerCount;}map[newPointerCount] = i;newPointerCount += 1;}}if (newPointerCount == 0) {throw new IllegalArgumentException("idBits did not match any ids in the event");}final int newAction;if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {if (newActionPointerIndex < 0) {// An unrelated pointer changed.newAction = ACTION_MOVE;} else if (newPointerCount == 1) {// The first/last pointer went down/up.newAction = oldActionMasked == ACTION_POINTER_DOWN? ACTION_DOWN : ACTION_UP;} else {// A secondary pointer went down/up.newAction = oldActionMasked| (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);}} else {// Simple up/down/cancel/move or other motion action.newAction = oldAction;}final int historySize = nativeGetHistorySize(mNativePtr);for (int h = 0; h <= historySize; h++) {final int historyPos = h == historySize ? HISTORY_CURRENT : h;for (int i = 0; i < newPointerCount; i++) {nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);}final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);if (h == 0) {ev.initialize(nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),nativeGetDisplayId(mNativePtr),newAction, nativeGetFlags(mNativePtr),nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,newPointerCount, pp, pc);} else {nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);}}return ev;}}

  事件分解发生在多点触控的时候,方法参数idBits是事件的id移位后的值,例如id=1,1<<id就得到2,这个时候idBits的值就是2。
  先看下获取MotionEvent的方法,MotionEvent ev = obtain(),

    static private MotionEvent obtain() {final MotionEvent ev;synchronized (gRecyclerLock) {ev = gRecyclerTop;if (ev == null) {return new MotionEvent();}gRecyclerTop = ev.mNext;gRecyclerUsed -= 1;}ev.mNext = null;ev.prepareForReuse();return ev;}

  该方法是从事件缓存中得到一个事件,事件缓存是一个链表,事件缓存最多是MAX_RECYCLED(10)个事件,如果没有缓存会先新建一个。该事件最终就是分解得到的新事件。
  回到split()函数里面,9行代码得到事件的pointer数量,15-18行会得到原事件的action和pointerIndex。
  代码21行开始循环,22行会将新事件的属性存放在pp数组(里面主要包括id和tooltype)中,通过原事件中id与新事件中的id进行匹配,会得到新事件的pointer数量newPointerCount,并且将原事件中新分解事件id对应pointerIndex存在map中,其中有个变量newActionPointerIndex,代表新事件中的pointerIndex。这个字段的赋值是有条件的,在匹配id的情况下,并且原事件中的index与原事件中的当前pointerIndex相等下情况下。这个变量newActionPointerIndex在新分解出来的事件的pointer数量多于1,并且在事件类型是ACTION_POINTER_DOWN或者ACTION_POINTER_UP的情况下,是会设置到C++类中的MotionEvent中的mAction里。从下面的代码分析中也能看出来。
  代码37-54行是为了获取新分解事件的action,在当前事件类型为ACTION_POINTER_DOWN或者ACTION_POINTER_UP的情况下,根据新分解事件的pointer序列和pointer数量来设置action的值。
  1、新分解事件的pointer序列的值小于0,action直接设置成ACTION_MOVE
  2、新分解事件的pointer序列的值不小于0,并且pointer数量=1,这种情况就是新分解的事件只有一个手指在触发触摸事件,pointer index也就是0。根据事件类型分别将action设置成ACTION_DOWN或ACTION_UP
  3、新分解事件的pointer序列的值不小于0,并且pointer数量>1,这种情况就是新分解的事件超过一个手指在触发触摸事件,需要将newActionPointerIndex的值设置在action值里面,oldActionMasked保持不变,还是ACTION_POINTER_DOWN或者ACTION_POINTER_UP。
  57行开始的循环是取出新分解事件对应的历史数据,然后设置到新分解事件中。
  60行开始取出原事件中新分解事件对应的序列的数据,放置到pc数组中。其中map数组中存放的是新分解事件的pointer index,通过map[i],historyPos,用nativeGetPointerCoords方法将原事件中的对应数据设置到pc数组中。65行通过h是否等于0,来执行ev.initialize方法还是nativeAddBatch方法。ev.initialize方法是初始化方法,会将事件的当前属性给初始化。而nativeAddBatch方法,是将历史数据设置到新分解事件中。


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

相关文章

【Android 事件分发】MotionEvent.ACTION_DOWN 按下事件分发流程( Activity | ViewGroup | View )

Android 事件分发 系列文章目录 【Android 事件分发】事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) 【Android 事件分发】事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -&#xff1e; PhoneWindow -&#xff1e; De…

Android MotionEvent详解

在前边几篇博文中&#xff08;《图解Android事件传递之ViewGroup篇》&#xff0c;《图解Android事件传递之View篇》&#xff09;&#xff0c;我们已经了解了android触摸事件传递机制&#xff0c;接着我们再来研究一下与触摸事件传递相关的几个比较重要的类&#xff0c;比如Moti…

[Android Input系统]MotionEvent的序列化传送

这里从云游戏的触控操作看起&#xff0c; PC端的客户端支持按键和鼠标滑动操作&#xff0c;手机上的云游戏客户端则是和手机游戏一样的touch触控&#xff0c;客户端的touch操作是怎样处理给服务端的呢&#xff0c;猜测是把touch操作“实时”的传送给了服务器&#xff0c;Androi…

Android MotionEvent 坐标获取

Android MotionEvent中getX()与getRawX()都是获取屏幕坐标&#xff08;横&#xff09;&#xff0c;但二者又有区别 getX() &#xff1a; 是获取相对当前控件&#xff08;View&#xff09;的坐标 getRawX() &#xff1a; 是获取相对显示屏幕左上角的坐标 演示…

Android事件拦截(一)——触摸事件MotionEvent分析

&#xff08;1&#xff09;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…

Android MotionEvent

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

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

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

MotionEvent 详解

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

MotionLayout MotionScene 动画从未如此简单!

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

Android的MotionEvent和事件处理

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

MotionEvent详解

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

Android自定义View进阶-MotionEvent详解

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

常用awk命令整理

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

sed命令和awk命令

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

awk命令应用

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

概要设计之功能模块

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

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

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

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

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

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

撰写的设计文档主要分为&#xff1a;总体概要设计文档 详细设计文档&#xff0c;后简称为“概设”“详设”。 总设和详设都应该包含的部分&#xff1a; &#xff08;1&#xff09; 需求&#xff1a;一般以产品的语言描述&#xff0c;这一块可以拷贝产品需求文档中的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 &#xff1a;静态页面 4 3.2.2 模块 CM2&#xff1a;系统登录 5 3.2.3 模块 CM3 &#xff1a;注…