slot 协议
对于触摸屏的 motion event,驱动可以使用 A 或 B 协议来上报事件数据。其中,B 协议,也称之为 slot 协议,也是最常用的协议。因此,本文使用 slot 协议来分析 motion event 的分发。
我们可以先通过执行命令 adb shell getevent -l ,来观察 slot 协议上报的数据。
当手指按下时,上报的数据如下
EV_ABS ABS_MT_SLOT 00000000
EV_ABS ABS_MT_TRACKING_ID 00000000
EV_ABS ABS_MT_POSITION_X 000002ea
EV_ABS ABS_MT_POSITION_Y 00000534
EV_SYN SYN_REPORT 00000000
每一行数据,由 type、code、value 组成。如果 type 为 EV_ABS,那这一行是 motion event 的某个数据。如果 type 为 EV_SYN,它代表一个同步事件,并非 motion event 数据,表示系统要同步前面上报的 motion event 数据。
motion event 数据中,code 值的意思如下
- ABS_MT_SLOT : 表示驱动使用哪个 slot 上报 motion event 数据。
- ABS_MT_TRACKING_ID : 代表手指 ID。
- ABS_MT_POSITION_X/ABS_MT_POSITION_Y : 代表 motion event 的 x/y 坐标。
当手指移动时,上报的数据如下
EV_ABS ABS_MT_POSITION_X 000002ec
EV_ABS ABS_MT_POSITION_Y 00000526
EV_SYN SYN_REPORT 00000000
移动事件的数据中,并没有看到 ABS_MT_SLOT 和 ABS_MT_TRACKING_ID 数据。当系统同步数据时,会沿用上一次事件的 ABS_MT_SLOT 和 ABS_MT_TRACKING_ID 数据。
当手指抬起时,驱动上报的数据如下
EV_ABS ABS_MT_TRACKING_ID ffffffff
EV_SYN SYN_REPORT 00000000
抬起事件的数据中,也没有看到 ABS_MT_SLOT 数据,同样沿用上一次事件的数据。但是注意, ABS_MT_TRACKING_ID 的值为 ffffffff,十进制值是 -1,它是一个无效值,代表手指抬起。
InputReader 处理 motion event
Android U Input系统:InputReader 加工按键事件 分析了 InputReader 处理按键事件的流程,它会把事件交给输入设备 InputDevice 的 InputMapper 处理。对于 motion event 来说,同样适配这套处理流程。
当前,大部分触摸设备都支持多点触摸,因此,motion event 会交给 MultiTouchInputMapper 处理,如下
std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent* rawEvent) {
// 父类 TouchInputMapper 处理同步事件
std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);
// 使用累加器收集 motion event 数据
mMultiTouchMotionAccumulator.process(rawEvent);
return out;
}
MultiTouchInputMapper 先使用自己的累加器来收集 motion event 数据,如下
void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
if (rawEvent->type == EV_ABS) {
bool newSlot = false;
if (mUsingSlotsProtocol) {
// 获取 slot 数据
// 注意,如果没有 slot 数据,那么就直接使用上一次数据的 slot 值,即 mCurrentSlot
if (rawEvent->code == ABS_MT_SLOT) {
mCurrentSlot = rawEvent->value;
newSlot = true;
}
} else if (mCurrentSlot < 0) {
// ...
}
if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) {
// ...
} else {
// slot 值作为 index,从数组中获取 Slot 元素
Slot& slot = mSlots[mCurrentSlot];
// ...
switch (rawEvent->code) {
case ABS_MT_POSITION_X:
slot.mAbsMtPositionX = rawEvent->value;
warnIfNotInUse(*rawEvent, slot);
break;
case ABS_MT_POSITION_Y:
slot.mAbsMtPositionY = rawEvent->value;
warnIfNotInUse(*rawEvent, slot);
break;
// ...
case ABS_MT_TRACKING_ID:
if (mUsingSlotsProtocol && rawEvent->value < 0) {
// 手指 ID 大于 0,表示有效,因此标记 Slot 正在使用中
slot.mInUse = false;
} else {
// ...
}
break;
// ...
}
}
} else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
// ...
}
}
MultiTouchMotionAccumulator 把 motion event 数据,以 slot 值为索引,保存到数组 mSlots 对应的元素中。
MultiTouchInputMapper 利用累加器收集了 motion event 数据后,调用父类 TouchInputMapper 的方法来处理同步事件(type 为 EV_SYN,code 为 SYN_REPORT),如下
std::list<NotifyArgs> TouchInputMapper::process(const RawEvent* rawEvent) {
// ...
std::list<NotifyArgs> out;
// 处理同步事件
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
out += sync(rawEvent->when, rawEvent->readTime);
}
return out;
}
std::list<NotifyArgs> TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) {
std::list<NotifyArgs> out;
// ...
// mRawStatesPending 末尾插入一个元素
// Push a new state.
mRawStatesPending.emplace_back();
// 取出 mRawStatesPending 末尾元素
RawState& next = mRawStatesPending.back();
next.clear();
next.when = when;
next.readTime = readTime;
// ...
// 把子类收集的 motion event 数据,保存到 mRawStatesPending 末尾元素中
// Sync touch
syncTouch(when, &next);
// The last RawState is the actually second to last, since we just added a new state
const RawState& last =
mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1];
// ...
// 如果同步后的数据中没有 pointer id,那么沿用上一次事件的 pointer id
// Assign pointer ids.
if (!mHavePointerIds) {
assignPointerIds(last, next);
}
ALOGD_IF(debugRawEvents(),
"syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
"hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x",
last.rawPointerData.pointerCount, next.rawPointerData.pointerCount,
last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value,
last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value,
next.rawPointerData.canceledIdBits.value);
// ...
// 处理 mRawStatesPending 中同步过来的 motion event
out += processRawTouches(/*timeout=*/false);
return out;
}
TouchInputMapper 处理同步事件时,把实现类 MultitouchInputMapper 的累加器收集的 motion event 数据,同步到队列 mRawStatesPending 最后一个元素中,然后处理 mRawStatesPending。
MultiTouchInputMapper 同步数据
void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
size_t outCount = 0;
BitSet32 newPointerIdBits;
// 注意,这里强制把 mHavePointerIds 设置为 true,因此当前事件本来就是 motion event,一定会有 pointer id
mHavePointerIds = true;
for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
const MultiTouchMotionAccumulator::Slot& inSlot =
mMultiTouchMotionAccumulator.getSlot(inIndex);
// 填充 Slot 数据时,如果有 ABS_MT_TRACKING_ID 值(必须大于0),那么 Slot 就有效,否则无效
if (!inSlot.isInUse()) {
continue;
}
// ...
RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount];
outPointer.x = inSlot.getX();
outPointer.y = inSlot.getY();
// ...
// 下面一段代码,主要是根据 tracking id,填充 pointer id 数据
// Assign pointer id using tracking id if available.
if (mHavePointerIds) {
int32_t trackingId = inSlot.getTrackingId();
int32_t id = -1;
// 这一段代码,是为了获取 pointer id
if (trackingId >= 0) { // 上报的数据中有 tracking id
// 遍历 mPointerIdBits 保存的所有 pointer id,如果能从 mPointerTrackingIdMap 中
// 根据 pointer id 匹配到 tracking id,那么就使用这个 pointer id 作为此次同步数据的 pointer id
for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty();) {
uint32_t n = idBits.clearFirstMarkedBit();
if (mPointerTrackingIdMap[n] == trackingId) {
id = n;
}
}
// 没有从 mPointerTrackingIdMap 匹配到 pointer id,那么就生成一个 pointer id
if (id < 0 && !mPointerIdBits.isFull()) {
id = mPointerIdBits.markFirstUnmarkedBit();
mPointerTrackingIdMap[id] = trackingId;
}
}
if (id < 0) { // 没有获取到 pointer id,其实代表没有上报 tracking id
mHavePointerIds = false;
outState->rawPointerData.clearIdBits();
newPointerIdBits.clear();
} else { // 成功获取到 pointer id
// 保存 pointer id
outPointer.id = id;
// id -> index,index outState->rawPointerData.pointers 数组的索引,它保存了 motion event 的数据
outState->rawPointerData.idToIndex[id] = outCount;
outState->rawPointerData.markIdBit(id, isHovering);
// newPointerIdBits 保存手指 id
newPointerIdBits.markBit(id);
}
}
outCount += 1;
}
// 保存手指数量
outState->rawPointerData.pointerCount = outCount;
// MultiTouchInputMapper::mPointerIdBits 保存所有 pointer id
mPointerIdBits = newPointerIdBits;
// 如果使用 slot 协议,实现为空
mMultiTouchMotionAccumulator.finishSync();
}
所谓的同步数据,其实就是把 MultitouchInputMapper 累加器的 mSlots 数组中所有数据,全部同步到 RawState 中,当然还会额外生成并保存一些数据,例如,pointer id。
跟着注释,看下 RawState 如何保存数据的,如下
// TouchInputMapper.cpp
struct RawState {
// ...
// 同步的数据,主要保存这个结构体中
RawPointerData rawPointerData{};
// ...
};
/* Raw data for a collection of pointers including a pointer id mapping table. */
struct RawPointerData {
// 保存一个手指的 motion event 数据
struct Pointer {
// 手指 id
uint32_t id{0xFFFFFFFF};
// x,y 坐标
int32_t x{};
int32_t y{};
// ...
};
// 所有手指数量
uint32_t pointerCount{};
// 这个数组保存所有手指的 motion event 数据
std::array<Pointer, MAX_POINTERS> pointers{};
// ...
// pointer id -> index
// index 是 pointers 数组的索引
IdToIndexArray idToIndex{};
// ...
};
TouchInputMapper 处理同步后的数据
现在,TouchInputMapper::mRawStatesPending 保存了同步数据,现在来处理它
std::list<NotifyArgs> TouchInputMapper::processRawTouches(bool timeout) {
std::list<NotifyArgs> out;
if (mDeviceMode == DeviceMode::DISABLED) {
// Do not process raw event while the device is disabled.
return out;
}
// Drain any pending touch states. The invariant here is that the mCurrentRawState is always
// valid and must go through the full cook and dispatch cycle. This ensures that anything
// touching the current state will only observe the events that have been dispatched to the
// rest of the pipeline.
const size_t N = mRawStatesPending.size();
size_t count;
for (count = 0; count < N; count++) {
const RawState& next = mRawStatesPending[count];
// 正常来说,mRawStatesPending 只会用最后一个元素来保存同步的数据,因此只需要处理最后一个元素即可
// 但是对于 stylus 设备来说,有时候不能立即处理 mRawStatesPending 中的最后一个元素
// 因此,在下一次同步后,mRawStatesPending 可能出现两个元素或者更多
// 因此,需要用一个 vector 来保存所有同步的数据
// A failure to assign the stylus id means that we're waiting on stylus data
// and so should defer the rest of the pipeline.
if (assignExternalStylusId(next, timeout)) {
break;
}
// All ready to go.
clearStylusDataPendingFlags();
// 保存当前正在处理的同步数据
mCurrentRawState = next;
if (mCurrentRawState.when < mLastRawState.when) {
mCurrentRawState.when = mLastRawState.when;
mCurrentRawState.readTime = mLastRawState.readTime;
}
// 加工和分发
out += cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime);
}
// 处理了多少数据,就移除多少个数据
if (count != 0) {
mRawStatesPending.erase(mRawStatesPending.begin(), mRawStatesPending.begin() + count);
}
if (mExternalStylusDataPending) {
// ...
}
return out;
}
std::list<NotifyArgs> TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) {
std::list<NotifyArgs> out;
// mCurrentCookedState 保存加工后的事件的数据
mCurrentCookedState.clear();
// ...
// Cook pointer data. This call populates the mCurrentCookedState.cookedPointerData structure
// with cooked pointer data that has the same ids and indices as the raw data.
// The following code can use either the raw or cooked data, as needed.
// 1. 加工 motion event
cookPointerData();
// ...
if (mDeviceMode == DeviceMode::POINTER) {
} else {
if (!mCurrentMotionAborted) {
// ...
// 2. 分发 motion event
out += dispatchTouches(when, readTime, policyFlags);
// ...
}
// ...
}
// ...
// Copy current touch to last touch in preparation for the next cycle.
mLastRawState = mCurrentRawState;
mLastCookedState = mCurrentCookedState;
return out;
}
TouchInputMapper 处理同步的数据的过程,分为加工和分发。
加工
void TouchInputMapper::cookPointerData() {
// mCurrentRawState 是当前正在处理的”同步后的事件”
uint32_t currentPointerCount = mCurrentRawState.rawPointerData.pointerCount;
// 加工后的事件,保存到 mCurrentCookedState
mCurrentCookedState.cookedPointerData.clear();
mCurrentCookedState.cookedPointerData.pointerCount = currentPointerCount;
mCurrentCookedState.cookedPointerData.touchingIdBits =
mCurrentRawState.rawPointerData.touchingIdBits;
// ...
// Walk through the the active pointers and map device coordinates onto
// display coordinates and adjust for display orientation.
for (uint32_t i = 0; i < currentPointerCount; i++) {
const RawPointerData::Pointer& in = mCurrentRawState.rawPointerData.pointers[i];
// ...
vec2 transformed = {in.x, in.y};
mAffineTransform.applyTo(transformed.x /*byRef*/, transformed.y /*byRef*/);
// 把触摸屏坐标,转换成显示屏坐标
transformed = mRawToDisplay.transform(transformed);
// Write output coords.
PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i];
out.clear();
out.setAxisValue(AMOTION_EVENT_AXIS_X, transformed.x);
out.setAxisValue(AMOTION_EVENT_AXIS_Y, transformed.y);
// ...
uint32_t id = in.id;
// ...
// Write output properties.
PointerProperties& properties = mCurrentCookedState.cookedPointerData.pointerProperties[i];
properties.clear();
properties.id = id;
properties.toolType = in.toolType;
// 保存 pointer id -> index
// index 是 mCurrentRawState.rawPointerData.pointers 数组的索引
mCurrentCookedState.cookedPointerData.idToIndex[id] = i;
mCurrentCookedState.cookedPointerData.validIdBits.markBit(id);
}
}
所谓加工数据,只是对同步数据中的一部分数据进行转换,例如,把触摸屏的坐标,转换为显示屏的坐标。
分发
std::list<NotifyArgs> TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime,
uint32_t policyFlags) {
std::list<NotifyArgs> out;
// 当前加工数据的所有手指id
BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
// 前一次加工数据的所有手指id
BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
// ...
if (currentIdBits == lastIdBits) { // 前后两次手指id,没有改变
if (!currentIdBits.isEmpty()) { // 当前数据有手指 id
// 这种情况是 move event,创建一个 AMOTION_EVENT_ACTION_MOVE 事件
// No pointer id changes so this is a move event.
// The listener takes care of batching moves so we don't have to deal with that here.
out.push_back(
dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE,
0, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits,
-1, mOrientedXPrecision, mOrientedYPrecision, mDownTime,
MotionClassification::NONE));
}
} else { // 前后两次数据的手指 id 改变了
// 检测哪些手指抬起,哪些手指按下,哪些手指一动
// There may be pointers going up and pointers going down and pointers moving
// all at the same time.
BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value);
BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value);
BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
BitSet32 dispatchedIdBits(lastIdBits.value);
// 检测是否有手指移动,如果有,返回 true
// 例如,坐标改变,代表有手指移动
// Update last coordinates of pointers that have moved so that we observe the new
// pointer positions at the same time as other pointers that have just gone up.
bool moveNeeded =
updateMovedPointers(mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex,
mLastCookedState.cookedPointerData.pointerProperties,
mLastCookedState.cookedPointerData.pointerCoords,
mLastCookedState.cookedPointerData.idToIndex, moveIdBits);
// ...
// 有手指抬起,那么为所有手指,都创建一个 AMOTION_EVENT_ACTION_POINTER_UP 事件
while (!upIdBits.isEmpty()) {
uint32_t upId = upIdBits.clearFirstMarkedBit();
bool isCanceled = mCurrentCookedState.cookedPointerData.canceledIdBits.hasBit(upId);
if (isCanceled) {
ALOGI("Canceling pointer %d for the palm event was detected.", upId);
}
out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
AMOTION_EVENT_ACTION_POINTER_UP, 0,
isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState,
buttonState, 0,
mLastCookedState.cookedPointerData.pointerProperties,
mLastCookedState.cookedPointerData.pointerCoords,
mLastCookedState.cookedPointerData.idToIndex,
dispatchedIdBits, upId, mOrientedXPrecision,
mOrientedYPrecision, mDownTime,
MotionClassification::NONE));
dispatchedIdBits.clearBit(upId);
mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId);
}
// 如果有手指移动,那么创建一个 AMOTION_EVENT_ACTION_MOVE 事件
// Dispatch move events if any of the remaining pointers moved from their old locations.
// Although applications receive new locations as part of individual pointer up
// events, they do not generally handle them except when presented in a move event.
if (moveNeeded && !moveIdBits.isEmpty()) {
ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState, 0,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex,
dispatchedIdBits, -1, mOrientedXPrecision,
mOrientedYPrecision, mDownTime,
MotionClassification::NONE));
}
// 为每一个按下的手指,创建一个 AMOTION_EVENT_ACTION_POINTER_DOWN 事件
// Dispatch pointer down events using the new pointer locations.
while (!downIdBits.isEmpty()) {
uint32_t downId = downIdBits.clearFirstMarkedBit();
dispatchedIdBits.markBit(downId);
if (dispatchedIdBits.count() == 1) {
// First pointer is going down. Set down time.
mDownTime = when;
}
out.push_back(
dispatchMotion(when, readTime, policyFlags, mSource,
AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState,
0, mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex,
dispatchedIdBits, downId, mOrientedXPrecision,
mOrientedYPrecision, mDownTime, MotionClassification::NONE));
}
}
return out;
}
所谓的分发,其实就是对元数据进行解析,生成高级事件,例如 ACTION DOWN , ACTION MOVE, ACTION UP 等等。
那么,如何分辨应该创建哪种高级事件呢?原理是,利用前后两次加工后的数据的所有手指 ID!
如果前后两次的手指 ID 没有改变,那么代表手指发生移动,因此需要生成 move event。
如果前后两次手指 ID 有改变,那么必须检测到底哪些手指按下,哪些手指抬起,哪些手指移动,才能创建对应的高级事件。检测的标准是,前后两次手指ID,以一种恰当的方式做位操作,大家自己品。
不知道大家注意到没有,对于 move event,并没有分别为所有手指,都生成一个事件。而 down event,up event,是分别为每一个手指,单独生成一个事件。
那么,如何获取不同手指的触摸坐标呢?我曾经写过一篇多点触控的文章 多手指触控,其实也不是很难,当收到 ACTION_DOWN 、ACTION_POINTER_DOWN 事件时,获取不同手指 ID。当收到一个 MotionEvent.ACTION_MOVE 事件时,可以通过不同的手指 ID,分别获取对应的手指触摸坐标。
再回头看这里的代码,如果有多个手指移动,只创建了一个 move event,而这个事件包含了所有手指的触摸坐标数据。因此,可以通过不同手指 ID,通过一个 ACTION_MOVE 事件,能获取到所有手指的触摸坐标。
现在来看下创建高级事件的过程,这对于理解 app 开发的多点指触摸功能,非常关键,如下
NotifyMotionArgs TouchInputMapper::dispatchMotion(
nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action,
int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords,
const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
float yPrecision, nsecs_t downTime, MotionClassification classification) {
// 这两个数组,用于复制所有手指的 motion event 数据
PointerCoords pointerCoords[MAX_POINTERS];
PointerProperties pointerProperties[MAX_POINTERS];
uint32_t pointerCount = 0;
// idBits 保存的是所有手指ID
while (!idBits.isEmpty()) {
uint32_t id = idBits.clearFirstMarkedBit();
uint32_t index = idToIndex[id];
// 复制对应手指的 motion event 数据
pointerProperties[pointerCount].copyFrom(properties[index]);
pointerCoords[pointerCount].copyFrom(coords[index]);
// changedId 大于 0,表示只有 changeId 对应的手指需要生成高级事件,例如,down event,up event
// changedId 等于 -1,表示需要为 idBits 保存的所有手指,生成高级事件,例如 move event
// 所以,这里表示,如果只有一个手指需要生成高级事件,那么在 action 的 8 bit 位之前,保存数组 index
// 这个数组指的是 pointerProperties 和 pointerCoords
if (changedId >= 0 && id == uint32_t(changedId)) {
action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
}
pointerCount += 1;
}
ALOG_ASSERT(pointerCount != 0);
if (changedId >= 0 && pointerCount == 1) { // 单手指触摸,并且需要生成高级事件
// Replace initial down and final up action.
// We can compare the action without masking off the changed pointer index
// because we know the index is 0.
if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) {
// AMOTION_EVENT_ACTION_POINTER_DOWN 转换为 AMOTION_EVENT_ACTION_DOWN
action = AMOTION_EVENT_ACTION_DOWN;
} else if (action == AMOTION_EVENT_ACTION_POINTER_UP) {
if ((flags & AMOTION_EVENT_FLAG_CANCELED) != 0) {
action = AMOTION_EVENT_ACTION_CANCEL;
} else {
// AMOTION_EVENT_ACTION_POINTER_UP 转化为 AMOTION_EVENT_ACTION_UP
action = AMOTION_EVENT_ACTION_UP;
}
} else {
// Can't happen.
ALOG_ASSERT(false);
}
}
// ...
// 3 创建 NotifyMotionArgs
return NotifyMotionArgs(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
policyFlags, action, actionButton, flags, metaState, buttonState,
classification, edgeFlags, pointerCount, pointerProperties,
pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
downTime, std::move(frames));
}
如果直接解释这里的代码,你可能会感觉非常晦涩难懂。然而,如果我以 app 开发多点触控功能的知识要点为例,你可能就会轻易理解这里的代码。
在多点触控功能中
- 当第一个手指按下时,收到的 ACTION_DOWN 事件,那是因为这里针对单手指事件,把 AMOTION_EVENT_ACTION_POINTER_DOWN 转换为 AMOTION_EVENT_ACTION_DOWN。
- 当最后一个手指抬起,收到的 ACTION_UP ,而非 ACTION_POINTER_UP,道理同上。
- 当手指按下时,可以通过 action 获取 index,因为这里在 action 中混入 index。然后可以通过 index 转化为 pointer id,是因为通过 index,可以从 pointerProperties 数组中获取元素 PointerProperties,而 PointerProperties::id 正好就是 pointer id。
- 在收到 ACTION_MOVE 事件时,可以根据不同的手指 ID,获取触摸事件的 x,y 坐标。是因为可以把 pointer id 转化为 index,在以 index 为索引,从 pointerCoords 数组中获取对应手指的 x,y 坐标。如何把 pointer id 转化为 index 呢?由于 PointerProperties::id 保存是就是 pointer id,因此可以遍历数组 pointerProperties(元素类型为 PointerProperties),匹配 pointer id 匹配成功,就可以获取遍历时使用的 index。