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中的数据存放位置如下图
mSampleEventTimes中存放的是每个sample对应的时间。
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位。
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中。
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<
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方法,是将历史数据设置到新分解事件中。