Input系統(tǒng)之InputReader處理觸摸事件案例
正文
手機一般有兩種類型的輸入設備。一種是鍵盤類型的輸入設備,通常它包含電源鍵和音量下鍵。另一種是觸摸類型的輸入設備,觸摸屏就屬于這種類型。
鍵盤類型的輸入設備一般都是產(chǎn)生按鍵事件,前面已經(jīng)用幾篇文章,分析了按鍵事件的分發(fā)流程。
觸摸類型的輸入設備一般都是產(chǎn)生觸摸事件,本文就開始分析觸摸事件的分發(fā)流程。
1. InputMapper 處理觸摸事件
由 Input系統(tǒng): InputReader 處理按鍵事件 可知,InputReader 從 EventHub 獲取到事件后,最終把事件交給 InputMapper 進行處理。
InputMapperKeyboardInputMapperTouchInputMapperSingleTouchInputMapperMultiTouchInputMapper
對于鍵盤類型的輸入設備,它的按鍵事件由 KeyboardInputManager 處理。對于觸摸類型的輸入設備,如果設備支持多點觸摸,它的觸摸事件由 MultiTouchInputMapper 處理,而如果只支持單點觸摸,它的觸摸事件由 SingleTouchInputMapper 處理。
通常,手機上的觸摸屏都是支持多點觸摸的,那么就看看 MultiTouchInputMapper 處理觸摸事件的流程
void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
// 2. 調(diào)用父類處理同步事件(EV_SYN SYN_REPORT)
TouchInputMapper::process(rawEvent);
// 1. 使用累加器收集同步事件之前的每一個手指的觸控點信息
mMultiTouchMotionAccumulator.process(rawEvent);
}
為了方便大家理解這里的處理過程,我展示一段在觸摸屏上滑動手指所產(chǎn)生的觸摸事件序列
/dev/input/event4: EV_ABS ABS_MT_POSITION_X 00000336 /dev/input/event4: EV_ABS ABS_MT_POSITION_Y 0000017f /dev/input/event4: EV_SYN SYN_REPORT 00000000 /dev/input/event4: EV_ABS ABS_MT_POSITION_X 00000333 /dev/input/event4: EV_ABS ABS_MT_POSITION_Y 00000184 /dev/input/event4: EV_SYN SYN_REPORT 00000000 /dev/input/event4: EV_ABS ABS_MT_POSITION_X 0000032f /dev/input/event4: EV_ABS ABS_MT_POSITION_Y 00000188 /dev/input/event4: EV_SYN SYN_REPORT 00000000
對于每一次的觸摸事件,例如手指按下或者移動,驅(qū)動會先上報它的信息事件,例如 x, y 坐標事件,再加上一個同步事件(SYN_REPORT)。
那么,MultiTouchInputMapper 處理觸摸事件的過程就很好理解了,如下
- 使用累加器 MultiTouchMotionAccumulator 收集觸摸事件的信息。參考【2. 收集觸摸事件信息】
- 調(diào)用父類 TouchInputMapper::process() 處理同步事件。參考 【3. 處理同步事件】
2. 收集觸摸事件信息
在分析累加器收集觸摸事件信息之前,首先得理解多點觸摸協(xié)議,也就是 A / B 協(xié)議。B 協(xié)議也叫 slot 協(xié)議,下面簡單介紹下這個協(xié)議。
當?shù)谝粋€手指按下時,會有如下事件序列
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
事件 ABS_MT_SLOT,表明觸摸信息事件,是由哪個槽(slot)進行上報的。一個手指產(chǎn)生的觸摸事件,只能由同一個槽進行上報。
事件 ABS_MT_TRACKING_ID ,表示手指ID。手指 ID 才能唯一代表一個手指,槽的 ID 并不能代表一個手指。因為假如一個手指抬起,另外一個手指按下,這兩個手指的事件可能由同一個槽進行上報,但是手指 ID 肯定是不一樣的。
事件 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 表示觸摸點的 x, y 坐標值。
事件 SYN_REPORT 是同步事件,它表示系統(tǒng)需要同步并處理之前的事件。
當?shù)谝粋€手指移動時,會有如下事件
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 事件,默認使用前面的值,因為此時只有一個手指。
當?shù)诙€手指按下時,會有如下事件
EV_ABS ABS_MT_SLOT 00000001 EV_ABS ABS_MT_TRACKING_ID 00000001 EV_ABS ABS_MT_POSITION_X 00000470 EV_ABS ABS_MT_POSITION_Y 00000475 EV_SYN SYN_REPORT 00000000
很簡單,第二個手指的事件,由另外一個槽進行上報。
當兩個手指同時移動時,會有如下事件
EV_ABS ABS_MT_SLOT 00000000 EV_ABS ABS_MT_POSITION_Y 000004e0 EV_ABS ABS_MT_SLOT 00000001 EV_ABS ABS_MT_POSITION_X 0000046f EV_ABS ABS_MT_POSITION_Y 00000414 EV_SYN SYN_REPORT 00000000
通過指定槽,就可以清晰看到事件由哪個槽進行上報,從而就可以區(qū)分出兩個手指產(chǎn)生的事件。
當其中一個手指抬起時,會有如下事件
EV_ABS ABS_MT_SLOT 00000000 // 注意,ABS_MT_TRACKING_ID 的值為 -1 EV_ABS ABS_MT_TRACKING_ID ffffffff EV_ABS ABS_MT_SLOT 00000001 EV_ABS ABS_MT_POSITION_Y 000003ee EV_SYN SYN_REPORT 00000000
當一個手指抬起時,ABS_MT_TRACKING_ID 事件的值為 -1,也就是十六進制的 ffffffff。通過槽事件,可以知道是第一個手指抬起了。
如果最后一個手指也抬起了,會有如下事件
EV_ABS ABS_MT_TRACKING_ID ffffffff // 同步事件,不屬于觸摸事件 EV_SYN SYN_REPORT 00000000
通過 ABS_MT_TRACKING_ID 事件可知,手指是抬起了,但是哪個手指抬起了呢?由于抬起的是最后一個手指,因此省略了槽事件。
現(xiàn)在已經(jīng)了解了 slot 協(xié)議,現(xiàn)在讓我來看看累加器 MultiTouchMotionAccumulator 是如何收集這個協(xié)議上報的數(shù)據(jù)的
void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
if (rawEvent->type == EV_ABS) {
bool newSlot = false;
if (mUsingSlotsProtocol) {
// 1. SLOT 協(xié)議,使用 ABS_MT_SLOT 事件獲取索引
if (rawEvent->code == ABS_MT_SLOT) {
mCurrentSlot = rawEvent->value;
newSlot = true;
}
} else if (mCurrentSlot < 0) {
// 非 SLOT 協(xié)議 : 初始上報的事件,默認 slot 為 0
mCurrentSlot = 0;
}
if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) {
// ...
} else {
// 2. 根據(jù)索引,獲取 Slot 數(shù)組的元素,并填充信息
Slot* slot = &mSlots[mCurrentSlot];
if (!mUsingSlotsProtocol) {
slot->mInUse = true;
}
switch (rawEvent->code) {
case ABS_MT_POSITION_X:
slot->mAbsMTPositionX = rawEvent->value;
break;
case ABS_MT_POSITION_Y:
slot->mAbsMTPositionY = rawEvent->value;
break;
// ...
case ABS_MT_TRACKING_ID:
if (mUsingSlotsProtocol && rawEvent->value < 0) {
// The slot is no longer in use but it retains its previous contents,
// which may be reused for subsequent touches.
// SLOT 協(xié)議: ABS_MT_TRACKING_ID 事件的值小于0,表示當前 slot 不再使用。
slot->mInUse = false;
} else {
// SLOT 協(xié)議 : ABS_MT_TRACKING_ID 事件的值為非負值,表示當前 slot 正在使用。
slot->mInUse = true;
slot->mAbsMTTrackingId = rawEvent->value;
}
break;
// ...
}
}
} else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
// MultiTouch Sync: The driver has returned all data for *one* of the pointers.
// 非 SLOT 協(xié)議 : EV_SYN + SYN_MT_REPORT 事件,分割手指的觸控點信息
mCurrentSlot += 1;
}
}
收集 slot 協(xié)議上報的數(shù)據(jù)的過程如下
- 首先根據(jù) ABS_MT_SLOT 事件,獲取數(shù)組索引。如果上報的數(shù)據(jù)中沒有指定 ABS_MT_SLOT 事件,那么默認用最近一次的 ABS_MT_SLOT 事件的值。
- 根據(jù)索引,從數(shù)組 mSlots 獲取 Slot 元素,并填充數(shù)據(jù)。
很簡單,就是用 Slot 數(shù)組的不同元素,收集不同手指所產(chǎn)生的事件信息。
3. 處理同步事件
根據(jù)前面的分析可知,驅(qū)動每次上報完觸摸事件信息后,都會伴隨著一個同步事件。剛才已經(jīng)收集了觸摸事件的信息,現(xiàn)在來看下如何處理同步事件
void TouchInputMapper::process(const RawEvent* rawEvent) {
mCursorButtonAccumulator.process(rawEvent);
mCursorScrollAccumulator.process(rawEvent);
mTouchButtonAccumulator.process(rawEvent);
// 處理同步事件
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
sync(rawEvent->when, rawEvent->readTime);
}
}
void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) {
// Push a new state.
// 添加一個空的元素
mRawStatesPending.emplace_back();
// 獲取剛剛添加的元素
RawState& next = mRawStatesPending.back();
next.clear();
next.when = when;
next.readTime = readTime;
// ...
// 1. 同步累加器中的數(shù)據(jù)到 next 中
// syncTouch() 由子類實現(xiàn)
syncTouch(when, &next);
// ...
// 2. 處理數(shù)據(jù)
processRawTouches(false /*timeout*/);
}
處理同步事件的過程如下
- 調(diào)用 syncTouch() 把累加器收集到數(shù)據(jù),同步到 mRawStatesPending 最后一個元素中。syncTouch() 由子類實現(xiàn)。參考【3.1 同步數(shù)據(jù)】
- 處理同步過來的數(shù)據(jù)。同步過來的數(shù)據(jù),基本上還是元數(shù)據(jù),因此需要對它加工,最終要生成高級事件,并分發(fā)出去。參考【3.2 處理同步后的數(shù)據(jù)】
3.1 同步數(shù)據(jù)
void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
size_t outCount = 0;
BitSet32 newPointerIdBits;
mHavePointerIds = true;
for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
// 從收集器中獲取 Slot 數(shù)組的元素
const MultiTouchMotionAccumulator::Slot* inSlot =
mMultiTouchMotionAccumulator.getSlot(inIndex);
// 如果 tracking id 為負值,槽就會不再使用
if (!inSlot->isInUse()) {
continue;
}
if (inSlot->getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM) {
// ...
}
if (outCount >= MAX_POINTERS) {
break; // too many fingers!
}
// 把累加器的Slot數(shù)組的數(shù)據(jù)同步到 RawState::rawPointerData 中
RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount];
outPointer.x = inSlot->getX();
outPointer.y = inSlot->getY();
outPointer.pressure = inSlot->getPressure();
outPointer.touchMajor = inSlot->getTouchMajor();
outPointer.touchMinor = inSlot->getTouchMinor();
outPointer.toolMajor = inSlot->getToolMajor();
outPointer.toolMinor = inSlot->getToolMinor();
outPointer.orientation = inSlot->getOrientation();
outPointer.distance = inSlot->getDistance();
outPointer.tiltX = 0;
outPointer.tiltY = 0;
outPointer.toolType = inSlot->getToolType();
if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
// ...
}
bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE &&
(mTouchButtonAccumulator.isHovering() ||
(mRawPointerAxes.pressure.valid && inSlot->getPressure() <= 0));
outPointer.isHovering = isHovering;
// Assign pointer id using tracking id if available.
if (mHavePointerIds) {
int32_t trackingId = inSlot->getTrackingId();
int32_t id = -1;
// 把 tracking id 轉(zhuǎn)化為 id
if (trackingId >= 0) {
// mPointerIdBits 保存的是手指的所有 id
// mPointerTrackingIdMap 是建立 id 到 trackingId 的映射
// 這里就是根據(jù) trackingId 找到 id
for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty();) {
uint32_t n = idBits.clearFirstMarkedBit();
if (mPointerTrackingIdMap[n] == trackingId) {
id = n;
}
}
// id < 0 表示從緩存中,根據(jù) trackingId, 沒有獲取到 id
if (id < 0 && !mPointerIdBits.isFull()) {
// 從 mPointerIdBits 生成一個 id
id = mPointerIdBits.markFirstUnmarkedBit();
// mPointerTrackingIdMap 建立 id 到 trackingId 映射
mPointerTrackingIdMap[id] = trackingId;
}
}
// id < 0,表示手指抬起
if (id < 0) {
mHavePointerIds = false;
// 清除對應的數(shù)據(jù)
outState->rawPointerData.clearIdBits();
newPointerIdBits.clear();
} else { // 有 id
// 保存id
outPointer.id = id;
// 保存 id -> index 映射
// index 是數(shù)組 RawPointerData::pointers 的索引
outState->rawPointerData.idToIndex[id] = outCount;
outState->rawPointerData.markIdBit(id, isHovering);
newPointerIdBits.markBit(id);
}
}
outCount += 1;
}
// 保存手指的數(shù)量
outState->rawPointerData.pointerCount = outCount;
// 保存所有的手指 id
mPointerIdBits = newPointerIdBits;
// 對于 SLOT 協(xié)議,同步的收尾工作不做任何事
mMultiTouchMotionAccumulator.finishSync();
}
累加器收集的數(shù)據(jù)是由驅(qū)動直接上報的元數(shù)據(jù),這里把元數(shù)據(jù)同步到 RawState::rawPointerData,它的類型為 RawPointerData ,結構體定義如下
// TouchInputMapper.h
/* Raw data for a collection of pointers including a pointer id mapping table. */
struct RawPointerData {
struct Pointer {
uint32_t id; // 手指的 ID
int32_t x;
int32_t y;
// ...
};
// 手指的數(shù)量
uint32_t pointerCount;
// 用 Pointer 數(shù)組保存觸摸事件的所有信息
Pointer pointers[MAX_POINTERS];
// touchingIdBits 保存所有手指的ID
BitSet32 hoveringIdBits, touchingIdBits, canceledIdBits;
// 建立手指ID到數(shù)組索引的映射
uint32_t idToIndex[MAX_POINTER_ID + 1];
// ...
};
介紹下 RawPointerData 的幾個成員變量,就可以知道同步后的數(shù)據(jù)有哪些了
- uint32_t pointerCount : 保存觸摸的手指數(shù)量。
- BitSet32 touchingIdBits : 保存所有手指的ID。
- Pointer pointers[MAX_POINTERS] : 保存所有手指的觸摸事件的元數(shù)據(jù)。
- uint32_t idToIndex[MAX_POINTER_ID + 1] : 保存手指 ID 到 index 的映射。這個 index 就是數(shù)組 pointers 的索引。
在這里,我要強調(diào)幾點事
- 只有手指 ID 才能唯一代表一個手指。
- index 只能作為數(shù)據(jù)的索引,來獲取手指的觸摸事件信息。
- 如果你知道了手指ID,那么就可以通過 idToIndex 獲取索引,然后根據(jù)索引獲取手指對應的觸摸事件信息。
我曾經(jīng)寫了一篇文章 多手指觸控,其實也不是很難 ,這篇文章中強調(diào)了,在多手指觸摸的情況下,只有手指 ID 能唯一代表一個手指,如果想獲取某一個手指的觸摸事件,那么必須先將 ID 轉(zhuǎn)化為 index,然后使用這個 index 從數(shù)組中獲取觸摸事件的數(shù)據(jù)。現(xiàn)在,你懂了嗎?
3.2 處理同步后的數(shù)據(jù)
現(xiàn)在數(shù)據(jù)已經(jīng)同步到 mRawStatesPending 最后一個元素中,但是這些數(shù)據(jù)基本上是元數(shù)據(jù),是比較晦澀的,接下來看看如何處理這些數(shù)據(jù)
void TouchInputMapper::processRawTouches(bool timeout) {
if (mDeviceMode == DeviceMode::DISABLED) {
// ...
}
// 現(xiàn)在開始處理同步過來的數(shù)據(jù)
const size_t N = mRawStatesPending.size();
size_t count;
for (count = 0; count < N; count++) {
// 獲取數(shù)據(jù)
const RawState& next = mRawStatesPending[count];
// ...
// 1. mCurrentRawState 保存當前正在處理的元數(shù)據(jù)
mCurrentRawState.copyFrom(next);
if (mCurrentRawState.when < mLastRawState.when) {
mCurrentRawState.when = mLastRawState.when;
mCurrentRawState.readTime = mLastRawState.readTime;
}
// 2. 加工以及分發(fā)
cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime);
}
// 成功處理完數(shù)據(jù),就從 mRawStatesPending 從擦除
if (count != 0) {
mRawStatesPending.erase(mRawStatesPending.begin(), mRawStatesPending.begin() + count);
}
if (mExternalStylusDataPending) {
// ...
}
}
開始處理元數(shù)據(jù)之前,首先使用 mCurrentRawState 復制了當前正在處理的數(shù)據(jù),后面會使用它進行前后兩次的數(shù)據(jù)對比,生成高級事件,例如 DOWN, MOVE, UP 事件。
然后調(diào)用 cookAndDispatch() 對數(shù)據(jù)進行加工和分發(fā)
void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) {
// 加工完的數(shù)據(jù)保存到 mCurrentCookedState
mCurrentCookedState.clear();
// ...
// Consume raw off-screen touches before cooking pointer data.
// If touches are consumed, subsequent code will not receive any pointer data.
if (consumeRawTouches(when, readTime, policyFlags)) {
mCurrentRawState.rawPointerData.clear();
}
// 1. 加工事件
cookPointerData();
// ...
// 此時的 device mode 為 DIRECT,表示直接分發(fā)
if (mDeviceMode == DeviceMode::POINTER) {
// ...
} else {
updateTouchSpots();
if (!mCurrentMotionAborted) {
dispatchButtonRelease(when, readTime, policyFlags);
dispatchHoverExit(when, readTime, policyFlags);
//2. 分發(fā)觸摸事件
dispatchTouches(when, readTime, policyFlags);
dispatchHoverEnterAndMove(when, readTime, policyFlags);
dispatchButtonPress(when, readTime, policyFlags);
}
if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
mCurrentMotionAborted = false;
}
}
// ...
// 保存上一次的元數(shù)據(jù)和上一次的加工后的數(shù)據(jù)
mLastRawState.copyFrom(mCurrentRawState);
mLastCookedState.copyFrom(mCurrentCookedState);
}
加工和分發(fā)事件的過程如下
- 使用 cookPointerData() 進行加工事件。加工什么呢?例如,由于手指是在輸入設備上觸摸的,因此需要把輸入設備的坐標轉(zhuǎn)換為顯示屏的坐標,這樣窗口就能接收到正確的坐標事件。參考【3.2.1 加工數(shù)據(jù)】
- 使用 dispatchTouches() 進行分發(fā)事件。底層上報的數(shù)據(jù)畢竟晦澀難懂,因此需要包裝成 DOWN/MOVE/UP 事件進行分發(fā)。參考【3.2.2 分發(fā)事件】
3.2.1 加工數(shù)據(jù)
void TouchInputMapper::cookPointerData() {
uint32_t currentPointerCount = mCurrentRawState.rawPointerData.pointerCount;
mCurrentCookedState.cookedPointerData.clear();
mCurrentCookedState.cookedPointerData.pointerCount = currentPointerCount;
mCurrentCookedState.cookedPointerData.hoveringIdBits =
mCurrentRawState.rawPointerData.hoveringIdBits;
mCurrentCookedState.cookedPointerData.touchingIdBits =
mCurrentRawState.rawPointerData.touchingIdBits;
mCurrentCookedState.cookedPointerData.canceledIdBits =
mCurrentRawState.rawPointerData.canceledIdBits;
if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
mCurrentCookedState.buttonState = 0;
} else {
mCurrentCookedState.buttonState = mCurrentRawState.buttonState;
}
// Walk through the the active pointers and map device coordinates onto
// surface coordinates and adjust for display orientation.
for (uint32_t i = 0; i < currentPointerCount; i++) {
const RawPointerData::Pointer& in = mCurrentRawState.rawPointerData.pointers[i];
// Size
// ...
// Pressure
// ...
// Distance
// ...
// Coverage
// ...
// Adjust X,Y coords for device calibration
float xTransformed = in.x, yTransformed = in.y;
mAffineTransform.applyTo(xTransformed, yTransformed);
// 1. 把輸入設備的坐標,轉(zhuǎn)換為顯示設備坐標
// 轉(zhuǎn)換后的坐標,保存到 xTransformed 和 yTransformed 中
rotateAndScale(xTransformed, yTransformed);
// Adjust X, Y, and coverage coords for surface orientation.
// ...
// Write output coords.
PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i];
out.clear();
out.setAxisValue(AMOTION_EVENT_AXIS_X, xTransformed);
out.setAxisValue(AMOTION_EVENT_AXIS_Y, yTransformed);
out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size);
out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor);
out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor);
out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt);
out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) {
out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left);
out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top);
out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right);
out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom);
} else {
out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
}
// Write output relative fields if applicable.
uint32_t id = in.id;
if (mSource == AINPUT_SOURCE_TOUCHPAD &&
mLastCookedState.cookedPointerData.hasPointerCoordsForId(id)) {
const PointerCoords& p = mLastCookedState.cookedPointerData.pointerCoordsForId(id);
float dx = xTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_X);
float dy = yTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_Y);
out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, dx);
out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy);
}
// Write output properties.
PointerProperties& properties = mCurrentCookedState.cookedPointerData.pointerProperties[i];
properties.clear();
properties.id = id;
properties.toolType = in.toolType;
// Write id index and mark id as valid.
mCurrentCookedState.cookedPointerData.idToIndex[id] = i;
mCurrentCookedState.cookedPointerData.validIdBits.markBit(id);
}
}
加工的元數(shù)據(jù)保存到了 CookedState::cookedPointerData 中,它的類型為 CookedPointerData ,結構體定義如下
struct CookedPointerData {
uint32_t pointerCount;
PointerProperties pointerProperties[MAX_POINTERS];
// 保存坐標數(shù)據(jù)
PointerCoords pointerCoords[MAX_POINTERS];
BitSet32 hoveringIdBits, touchingIdBits, canceledIdBits, validIdBits;
uint32_t idToIndex[MAX_POINTER_ID + 1];
// ...
};
一看就明白了什么意思把,就不過多介紹了。
對于手機來的觸摸屏來說,觸摸事件的加工,最主要的就是把觸摸屏的坐標點轉(zhuǎn)換為顯示屏的坐標點,如下
// Transform raw coordinate to surface coordinate
void TouchInputMapper::rotateAndScale(float& x, float& y) {
// Scale to surface coordinate.
// 1. 根據(jù)x,y的縮放比例,計算觸摸點在顯示設備的縮放坐標
const float xScaled = float(x - mRawPointerAxes.x.minValue) * mXScale;
const float yScaled = float(y - mRawPointerAxes.y.minValue) * mYScale;
const float xScaledMax = float(mRawPointerAxes.x.maxValue - x) * mXScale;
const float yScaledMax = float(mRawPointerAxes.y.maxValue - y) * mYScale;
// Rotate to surface coordinate.
// 0 - no swap and reverse.
// 90 - swap x/y and reverse y.
// 180 - reverse x, y.
// 270 - swap x/y and reverse x.
// 根據(jù)旋轉(zhuǎn)方向計算最終的顯示設備的x,y坐標值
switch (mSurfaceOrientation) {
case DISPLAY_ORIENTATION_0:
x = xScaled + mXTranslate;
y = yScaled + mYTranslate;
break;
case DISPLAY_ORIENTATION_90:
y = xScaledMax - (mRawSurfaceWidth - mSurfaceRight);
x = yScaled + mYTranslate;
break;
case DISPLAY_ORIENTATION_180:
x = xScaledMax - (mRawSurfaceWidth - mSurfaceRight);
y = yScaledMax - (mRawSurfaceHeight - mSurfaceBottom);
break;
case DISPLAY_ORIENTATION_270:
y = xScaled + mXTranslate;
x = yScaledMax - (mRawSurfaceHeight - mSurfaceBottom);
break;
default:
assert(false);
}
}
這是一道初中的坐標系轉(zhuǎn)換的數(shù)學題目,我就不獻丑去細致分析了,主要過程如下
- 首先根據(jù)坐標軸的縮放比例 mXScale 和 mYScale,計算觸摸屏的坐標點在顯示屏的坐標系中的x, y軸的縮放值。
- 根據(jù)顯示屏 x, y 軸的偏移量,以及旋轉(zhuǎn)角度,最終計算出顯示屏上的坐標點。
3.2.2 分發(fā)事件
元數(shù)據(jù)已經(jīng)加工完成,現(xiàn)在是時候來分發(fā)了
void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mCurrentCookedState.buttonState;
if (currentIdBits == lastIdBits) {
if (!currentIdBits.isEmpty()) {
// 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.
// 如果前后兩次數(shù)據(jù)的手指數(shù)沒有變化,并且當前的手指數(shù)不為0,那么此時事件肯定是移動事件,需要分發(fā) AMOTION_EVENT_ACTION_MOVE 事件
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);
}
} else { // 前后兩次數(shù)據(jù)的手指數(shù)不相等
// 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);
// 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.
// 參數(shù) moveIdBits 表示有移動的手指,這里檢測移動的手指,前后兩次數(shù)據(jù)有變化,那么表示需要分發(fā)一個移動事件
bool moveNeeded =
updateMovedPointers(mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex,
mLastCookedState.cookedPointerData.pointerProperties,
mLastCookedState.cookedPointerData.pointerCoords,
mLastCookedState.cookedPointerData.idToIndex, moveIdBits);
if (buttonState != mLastCookedState.buttonState) {
moveNeeded = true;
}
// Dispatch pointer up events.
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);
}
// 有手指抬起,分發(fā) AMOTION_EVENT_ACTION_POINTER_UP 事件
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);
dispatchedIdBits.clearBit(upId);
mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId);
}
// 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.
// 如果移動的手指前后兩次數(shù)據(jù)有變化,那么分發(fā)移動事件
if (moveNeeded && !moveIdBits.isEmpty()) {
ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
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);
}
// 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;
}
// 有手指按下,分發(fā) AMOTION_EVENT_ACTION_POINTER_DOWN
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);
}
}
}
分發(fā)事件的過程,其實就是對比前后兩次的數(shù)據(jù),生成高級事件 AMOTION_EVENT_ACTION_POINTER_DOWN, AMOTION_EVENT_ACTION_MOVE, AMOTION_EVENT_ACTION_POINTER_UP,然后調(diào)用 dispatchMotion() 分發(fā)這些高級事件。
void 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 PointerProperties* properties,
const PointerCoords* coords, const uint32_t* idToIndex,
BitSet32 idBits, int32_t changedId, float xPrecision,
float yPrecision, nsecs_t downTime) {
PointerCoords pointerCoords[MAX_POINTERS];
PointerProperties pointerProperties[MAX_POINTERS];
uint32_t pointerCount = 0;
while (!idBits.isEmpty()) {
uint32_t id = idBits.clearFirstMarkedBit();
uint32_t index = idToIndex[id];
pointerProperties[pointerCount].copyFrom(properties[index]);
pointerCoords[pointerCount].copyFrom(coords[index]);
// action 添加索引
// action 中前8位表示手指索引,后8位表示ACTION
if (changedId >= 0 && id == uint32_t(changedId)) {
action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
}
pointerCount += 1;
}
ALOG_ASSERT(pointerCount != 0);
// 當只有一個手指按下,發(fā)送 AMOTION_EVENT_ACTION_DOWN 事件。
// 但最后一個手指抬起時,發(fā)送 AMOTION_EVENT_ACTION_UP 事件。
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) {
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 {
action = AMOTION_EVENT_ACTION_UP;
}
} else {
// Can't happen.
ALOG_ASSERT(false);
}
}
float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
if (mDeviceMode == DeviceMode::POINTER) {
auto [x, y] = getMouseCursorPosition();
xCursorPosition = x;
yCursorPosition = y;
}
const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
const int32_t deviceId = getDeviceId();
std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
std::for_each(frames.begin(), frames.end(),
[this](TouchVideoFrame& frame) { frame.rotate(this->mSurfaceOrientation); });
// 把數(shù)據(jù)包裝成 NotifyMotionArgs,并加入到 QueuedInputListener 隊列
NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
policyFlags, action, actionButton, flags, metaState, buttonState,
MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
downTime, std::move(frames));
getListener()->notifyMotion(&args);
}
可以看到,數(shù)據(jù)最終被包裝成 NotifyMotionArgs 分發(fā)到下一環(huán) InputClassifier。
但是,在這之前,還對 action 做了如下處理
- 為 action 添加一個 index。由于 index 是元數(shù)據(jù)數(shù)組的索引,因此 action 也就是綁定了觸摸事件的數(shù)據(jù)。
- 如果是第一個手指按下,把 AMOTION_EVENT_ACTION_POINTER_DOWN 轉(zhuǎn)換為 AMOTION_EVENT_ACTION_DOWN。
- 如果是最后一個手指抬起,把 AMOTION_EVENT_ACTION_POINTER_UP 轉(zhuǎn)換成 AMOTION_EVENT_ACTION_UP。
第2點和第3點,在自定義 View 中處理多手指事件時,是不是很熟悉。
結束
閉上眼睛,想想 InputReader 如何處理觸摸事件的。其實就是通過 InputMapper 把觸摸屏的坐標點轉(zhuǎn)換為顯示屏的坐標點,然后對比前后兩次的數(shù)據(jù),生成高級事件,然后分發(fā)給下一環(huán)。so easy !
看我文章的人,是不是大部分是上層的人,前面兩篇文章正好是上層應用類型的文章,所以得到大量的點贊反饋。但是須知,經(jīng)濟基礎才能決定上層建筑,只有掌握了基礎,才能以不變應萬變。
關于觸摸事件,我也會打算寫一篇手勢導航的文章,也就是我們經(jīng)常使用的通過手勢進行返回,通過手勢回到桌面,這一定是大家最想看到的東西,更多關于InputReader處理觸摸事件的資料請關注腳本之家其它相關文章!
相關文章
android開發(fā)之方形圓角listview代碼分享
我寫這篇文章受到了kiritor的專欄發(fā)表的博文Android UI控件之ListView實現(xiàn)圓角效果的啟發(fā)。2013-06-06
Android實現(xiàn)ViewFlipper圖片動畫滑動
這篇文章主要為大家詳細介紹了Android實現(xiàn)ViewFlipper圖片動畫滑動,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05
Android Flutter實現(xiàn)自定義下拉刷新組件
在Flutter開發(fā)中官方提供了多平臺的下拉刷新組件供開發(fā)者使用。本文將改造一下這些組件,實現(xiàn)自定義的下拉刷新組件,感興趣的可以了解一下2022-08-08
Android編程實現(xiàn)WebView添加進度條的方法
這篇文章主要介紹了Android編程實現(xiàn)WebView添加進度條的方法,涉及Android WebView界面及控件功能相關操作技巧,需要的朋友可以參考下2017-02-02
Android Studio3.5開發(fā)工具(安卓開發(fā)工具)安裝步驟詳解
這篇文章主要為大家詳細介紹了Android Studio3.5開發(fā)工具安裝、安卓開發(fā)工具的安裝步驟,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-09-09

