热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

AndroidMotionEvent理解

Java层MotionEvent对应C层MotionEventJava层MotionEvent类下面的mNativePtr指向C层MotionEvent  C层MotionEven

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<   先看下获取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方法,是将历史数据设置到新分解事件中。


推荐阅读
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 解决.net项目中未注册“microsoft.ACE.oledb.12.0”提供程序的方法
    在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报错“未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序”。本文提供了解决这个问题的方法,包括错误描述和代码示例。通过注册提供程序和修改连接字符串,可以成功读取excel文件信息。 ... [详细]
  • 本文介绍了Codeforces Round #321 (Div. 2)比赛中的问题Kefa and Dishes,通过状压和spfa算法解决了这个问题。给定一个有向图,求在不超过m步的情况下,能获得的最大权值和。点不能重复走。文章详细介绍了问题的题意、解题思路和代码实现。 ... [详细]
author-avatar
推球了
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有