欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Input系統(tǒng)之InputReader處理觸摸事件案例

 更新時(shí)間:2023年02月14日 14:21:33   作者:大胃粥  
這篇文章主要為大家介紹了Input系統(tǒng)之InputReader處理觸摸事件案例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

手機(jī)一般有兩種類型的輸入設(shè)備。一種是鍵盤類型的輸入設(shè)備,通常它包含電源鍵和音量下鍵。另一種是觸摸類型的輸入設(shè)備,觸摸屏就屬于這種類型。

鍵盤類型的輸入設(shè)備一般都是產(chǎn)生按鍵事件,前面已經(jīng)用幾篇文章,分析了按鍵事件的分發(fā)流程。

觸摸類型的輸入設(shè)備一般都是產(chǎn)生觸摸事件,本文就開始分析觸摸事件的分發(fā)流程。

1. InputMapper 處理觸摸事件

Input系統(tǒng): InputReader 處理按鍵事件 可知,InputReader 從 EventHub 獲取到事件后,最終把事件交給 InputMapper 進(jìn)行處理。

InputMapperKeyboardInputMapperTouchInputMapperSingleTouchInputMapperMultiTouchInputMapper

對(duì)于鍵盤類型的輸入設(shè)備,它的按鍵事件由 KeyboardInputManager 處理。對(duì)于觸摸類型的輸入設(shè)備,如果設(shè)備支持多點(diǎn)觸摸,它的觸摸事件由 MultiTouchInputMapper 處理,而如果只支持單點(diǎn)觸摸,它的觸摸事件由 SingleTouchInputMapper 處理。

通常,手機(jī)上的觸摸屏都是支持多點(diǎn)觸摸的,那么就看看 MultiTouchInputMapper 處理觸摸事件的流程

void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
    // 2. 調(diào)用父類處理同步事件(EV_SYN SYN_REPORT)
    TouchInputMapper::process(rawEvent);
    // 1. 使用累加器收集同步事件之前的每一個(gè)手指的觸控點(diǎn)信息
    mMultiTouchMotionAccumulator.process(rawEvent);
}

為了方便大家理解這里的處理過程,我展示一段在觸摸屏上滑動(dòng)手指所產(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

對(duì)于每一次的觸摸事件,例如手指按下或者移動(dòng),驅(qū)動(dòng)會(huì)先上報(bào)它的信息事件,例如 x, y 坐標(biāo)事件,再加上一個(gè)同步事件(SYN_REPORT)。

那么,MultiTouchInputMapper 處理觸摸事件的過程就很好理解了,如下

  • 使用累加器 MultiTouchMotionAccumulator 收集觸摸事件的信息。參考【2. 收集觸摸事件信息
  • 調(diào)用父類 TouchInputMapper::process() 處理同步事件。參考 【3. 處理同步事件

2. 收集觸摸事件信息

在分析累加器收集觸摸事件信息之前,首先得理解多點(diǎn)觸摸協(xié)議,也就是 A / B 協(xié)議。B 協(xié)議也叫 slot 協(xié)議,下面簡單介紹下這個(gè)協(xié)議。

當(dāng)?shù)谝粋€(gè)手指按下時(shí),會(huì)有如下事件序列

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,表明觸摸信息事件,是由哪個(gè)槽(slot)進(jìn)行上報(bào)的。一個(gè)手指產(chǎn)生的觸摸事件,只能由同一個(gè)槽進(jìn)行上報(bào)。

事件 ABS_MT_TRACKING_ID ,表示手指ID。手指 ID 才能唯一代表一個(gè)手指,槽的 ID 并不能代表一個(gè)手指。因?yàn)榧偃缫粋€(gè)手指抬起,另外一個(gè)手指按下,這兩個(gè)手指的事件可能由同一個(gè)槽進(jìn)行上報(bào),但是手指 ID 肯定是不一樣的。

事件 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 表示觸摸點(diǎn)的 x, y 坐標(biāo)值。

事件 SYN_REPORT 是同步事件,它表示系統(tǒng)需要同步并處理之前的事件。

當(dāng)?shù)谝粋€(gè)手指移動(dòng)時(shí),會(huì)有如下事件

EV_ABS       ABS_MT_POSITION_X    000002ec            
EV_ABS       ABS_MT_POSITION_Y    00000526    
EV_SYN       SYN_REPORT           00000000 

此時(shí)沒有指定 ABS_MT_SLOT 事件和 ABS_MT_TRACKING_ID 事件,默認(rèn)使用前面的值,因?yàn)榇藭r(shí)只有一個(gè)手指。

當(dāng)?shù)诙€(gè)手指按下時(shí),會(huì)有如下事件

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 

很簡單,第二個(gè)手指的事件,由另外一個(gè)槽進(jìn)行上報(bào)。

當(dāng)兩個(gè)手指同時(shí)移動(dòng)時(shí),會(huì)有如下事件

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 

通過指定槽,就可以清晰看到事件由哪個(gè)槽進(jìn)行上報(bào),從而就可以區(qū)分出兩個(gè)手指產(chǎn)生的事件。

當(dāng)其中一個(gè)手指抬起時(shí),會(huì)有如下事件

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  

當(dāng)一個(gè)手指抬起時(shí),ABS_MT_TRACKING_ID 事件的值為 -1,也就是十六進(jìn)制的 ffffffff。通過槽事件,可以知道是第一個(gè)手指抬起了。

如果最后一個(gè)手指也抬起了,會(huì)有如下事件

EV_ABS       ABS_MT_TRACKING_ID   ffffffff     
// 同步事件,不屬于觸摸事件 
EV_SYN       SYN_REPORT           00000000    

通過 ABS_MT_TRACKING_ID 事件可知,手指是抬起了,但是哪個(gè)手指抬起了呢?由于抬起的是最后一個(gè)手指,因此省略了槽事件。

現(xiàn)在已經(jīng)了解了 slot 協(xié)議,現(xiàn)在讓我來看看累加器 MultiTouchMotionAccumulator 是如何收集這個(gè)協(xié)議上報(bào)的數(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é)議 : 初始上報(bào)的事件,默認(rèn) 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,表示當(dāng)前 slot 不再使用。
                        slot->mInUse = false;
                    } else {
                        // SLOT 協(xié)議 : ABS_MT_TRACKING_ID 事件的值為非負(fù)值,表示當(dāng)前 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 事件,分割手指的觸控點(diǎn)信息
        mCurrentSlot += 1;
    }
}

收集 slot 協(xié)議上報(bào)的數(shù)據(jù)的過程如下

  • 首先根據(jù) ABS_MT_SLOT 事件,獲取數(shù)組索引。如果上報(bào)的數(shù)據(jù)中沒有指定 ABS_MT_SLOT 事件,那么默認(rèn)用最近一次的 ABS_MT_SLOT 事件的值。
  • 根據(jù)索引,從數(shù)組 mSlots 獲取 Slot 元素,并填充數(shù)據(jù)。

很簡單,就是用 Slot 數(shù)組的不同元素,收集不同手指所產(chǎn)生的事件信息。

3. 處理同步事件

根據(jù)前面的分析可知,驅(qū)動(dòng)每次上報(bào)完觸摸事件信息后,都會(huì)伴隨著一個(gè)同步事件。剛才已經(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.
    // 添加一個(gè)空的元素
    mRawStatesPending.emplace_back();
    // 獲取剛剛添加的元素
    RawState& next = mRawStatesPending.back();
    next.clear();
    next.when = when;
    next.readTime = readTime;
    // ...
    // 1. 同步累加器中的數(shù)據(jù)到 next 中
    // syncTouch() 由子類實(shí)現(xiàn)
    syncTouch(when, &next);
    // ...
    // 2. 處理數(shù)據(jù)
    processRawTouches(false /*timeout*/);
}

處理同步事件的過程如下

  • 調(diào)用 syncTouch() 把累加器收集到數(shù)據(jù),同步到 mRawStatesPending 最后一個(gè)元素中。syncTouch() 由子類實(shí)現(xiàn)。參考【3.1 同步數(shù)據(jù)
  • 處理同步過來的數(shù)據(jù)。同步過來的數(shù)據(jù),基本上還是元數(shù)據(jù),因此需要對(duì)它加工,最終要生成高級(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 為負(fù)值,槽就會(huì)不再使用
        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 生成一個(gè) id 
                    id = mPointerIdBits.markFirstUnmarkedBit();
                    // mPointerTrackingIdMap 建立 id 到 trackingId 映射
                    mPointerTrackingIdMap[id] = trackingId;
                }
            }
            // id < 0,表示手指抬起
            if (id < 0) {
                mHavePointerIds = false;
                // 清除對(duì)應(yīng)的數(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;
    // 對(duì)于 SLOT 協(xié)議,同步的收尾工作不做任何事
    mMultiTouchMotionAccumulator.finishSync();
}

累加器收集的數(shù)據(jù)是由驅(qū)動(dòng)直接上報(bào)的元數(shù)據(jù),這里把元數(shù)據(jù)同步到 RawState::rawPointerData,它的類型為 RawPointerData ,結(jié)構(gòu)體定義如下

// 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 的幾個(gè)成員變量,就可以知道同步后的數(shù)據(jù)有哪些了

  • uint32_t pointerCount : 保存觸摸的手指數(shù)量。
  • BitSet32 touchingIdBits : 保存所有手指的ID。
  • Pointer pointers[MAX_POINTERS] : 保存所有手指的觸摸事件的元數(shù)據(jù)。
  • uint32_t idToIndex[MAX_POINTER_ID + 1] : 保存手指 ID 到 index 的映射。這個(gè) index 就是數(shù)組 pointers 的索引。

在這里,我要強(qiáng)調(diào)幾點(diǎn)事

  • 只有手指 ID 才能唯一代表一個(gè)手指。
  • index 只能作為數(shù)據(jù)的索引,來獲取手指的觸摸事件信息。
  • 如果你知道了手指ID,那么就可以通過 idToIndex 獲取索引,然后根據(jù)索引獲取手指對(duì)應(yīng)的觸摸事件信息。

我曾經(jīng)寫了一篇文章 多手指觸控,其實(shí)也不是很難 ,這篇文章中強(qiáng)調(diào)了,在多手指觸摸的情況下,只有手指 ID 能唯一代表一個(gè)手指,如果想獲取某一個(gè)手指的觸摸事件,那么必須先將 ID 轉(zhuǎn)化為 index,然后使用這個(gè) index 從數(shù)組中獲取觸摸事件的數(shù)據(jù)?,F(xiàn)在,你懂了嗎?

3.2 處理同步后的數(shù)據(jù)

現(xiàn)在數(shù)據(jù)已經(jīng)同步到 mRawStatesPending 最后一個(gè)元素中,但是這些數(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 保存當(dāng)前正在處理的元數(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 復(fù)制了當(dāng)前正在處理的數(shù)據(jù),后面會(huì)使用它進(jìn)行前后兩次的數(shù)據(jù)對(duì)比,生成高級(jí)事件,例如 DOWN, MOVE, UP 事件。

然后調(diào)用 cookAndDispatch() 對(duì)數(shù)據(jù)進(jìn)行加工和分發(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();
    // ...
    // 此時(shí)的 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() 進(jìn)行加工事件。加工什么呢?例如,由于手指是在輸入設(shè)備上觸摸的,因此需要把輸入設(shè)備的坐標(biāo)轉(zhuǎn)換為顯示屏的坐標(biāo),這樣窗口就能接收到正確的坐標(biāo)事件。參考【3.2.1 加工數(shù)據(jù)
  • 使用 dispatchTouches() 進(jìn)行分發(fā)事件。底層上報(bào)的數(shù)據(jù)畢竟晦澀難懂,因此需要包裝成 DOWN/MOVE/UP 事件進(jìn)行分發(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. 把輸入設(shè)備的坐標(biāo),轉(zhuǎn)換為顯示設(shè)備坐標(biāo)
        // 轉(zhuǎn)換后的坐標(biāo),保存到 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 ,結(jié)構(gòu)體定義如下

struct CookedPointerData {
    uint32_t pointerCount;
    PointerProperties pointerProperties[MAX_POINTERS];
    // 保存坐標(biāo)數(shù)據(jù)
    PointerCoords pointerCoords[MAX_POINTERS];
    BitSet32 hoveringIdBits, touchingIdBits, canceledIdBits, validIdBits;
    uint32_t idToIndex[MAX_POINTER_ID + 1];
    // ...
};

一看就明白了什么意思把,就不過多介紹了。

對(duì)于手機(jī)來的觸摸屏來說,觸摸事件的加工,最主要的就是把觸摸屏的坐標(biāo)點(diǎn)轉(zhuǎn)換為顯示屏的坐標(biāo)點(diǎn),如下

// Transform raw coordinate to surface coordinate
void TouchInputMapper::rotateAndScale(float& x, float& y) {
    // Scale to surface coordinate.
    // 1. 根據(jù)x,y的縮放比例,計(jì)算觸摸點(diǎn)在顯示設(shè)備的縮放坐標(biāo)
    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)方向計(jì)算最終的顯示設(shè)備的x,y坐標(biāo)值
    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);
    }
}

這是一道初中的坐標(biāo)系轉(zhuǎn)換的數(shù)學(xué)題目,我就不獻(xiàn)丑去細(xì)致分析了,主要過程如下

  • 首先根據(jù)坐標(biāo)軸的縮放比例 mXScalemYScale,計(jì)算觸摸屏的坐標(biāo)點(diǎn)在顯示屏的坐標(biāo)系中的x, y軸的縮放值。
  • 根據(jù)顯示屏 x, y 軸的偏移量,以及旋轉(zhuǎn)角度,最終計(jì)算出顯示屏上的坐標(biāo)點(diǎn)。

3.2.2 分發(fā)事件

元數(shù)據(jù)已經(jīng)加工完成,現(xiàn)在是時(shí)候來分發(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ù)沒有變化,并且當(dāng)前的手指數(shù)不為0,那么此時(shí)事件肯定是移動(dòng)事件,需要分發(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 表示有移動(dòng)的手指,這里檢測(cè)移動(dòng)的手指,前后兩次數(shù)據(jù)有變化,那么表示需要分發(fā)一個(gè)移動(dòng)事件
        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.
        // 如果移動(dòng)的手指前后兩次數(shù)據(jù)有變化,那么分發(fā)移動(dòng)事件
        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í)就是對(duì)比前后兩次的數(shù)據(jù),生成高級(jí)事件 AMOTION_EVENT_ACTION_POINTER_DOWN, AMOTION_EVENT_ACTION_MOVE, AMOTION_EVENT_ACTION_POINTER_UP,然后調(diào)用 dispatchMotion() 分發(fā)這些高級(jí)事件。

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);
    // 當(dāng)只有一個(gè)手指按下,發(fā)送 AMOTION_EVENT_ACTION_DOWN 事件。
    // 但最后一個(gè)手指抬起時(shí),發(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 隊(duì)列
    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。

但是,在這之前,還對(duì) action 做了如下處理

  • 為 action 添加一個(gè) index。由于 index 是元數(shù)據(jù)數(shù)組的索引,因此 action 也就是綁定了觸摸事件的數(shù)據(jù)。
  • 如果是第一個(gè)手指按下,把 AMOTION_EVENT_ACTION_POINTER_DOWN 轉(zhuǎn)換為 AMOTION_EVENT_ACTION_DOWN。
  • 如果是最后一個(gè)手指抬起,把 AMOTION_EVENT_ACTION_POINTER_UP 轉(zhuǎn)換成 AMOTION_EVENT_ACTION_UP。

第2點(diǎn)和第3點(diǎn),在自定義 View 中處理多手指事件時(shí),是不是很熟悉。

結(jié)束

閉上眼睛,想想 InputReader 如何處理觸摸事件的。其實(shí)就是通過 InputMapper 把觸摸屏的坐標(biāo)點(diǎn)轉(zhuǎn)換為顯示屏的坐標(biāo)點(diǎn),然后對(duì)比前后兩次的數(shù)據(jù),生成高級(jí)事件,然后分發(fā)給下一環(huán)。so easy !

看我文章的人,是不是大部分是上層的人,前面兩篇文章正好是上層應(yīng)用類型的文章,所以得到大量的點(diǎn)贊反饋。但是須知,經(jīng)濟(jì)基礎(chǔ)才能決定上層建筑,只有掌握了基礎(chǔ),才能以不變應(yīng)萬變。

關(guān)于觸摸事件,我也會(huì)打算寫一篇手勢(shì)導(dǎo)航的文章,也就是我們經(jīng)常使用的通過手勢(shì)進(jìn)行返回,通過手勢(shì)回到桌面,這一定是大家最想看到的東西,更多關(guān)于InputReader處理觸摸事件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論