ViewGroup事件分發總結-多點觸摸事件拆分

前言

《ViewGroup事件分發總結-TouchTarget》中對事件派發過程中TouchTarget的作用做了總結,TouchTarget中有一個成員變量pointerIdBits用於保存該child上的觸摸點ID集合,ViewGroup在實際派發過程中會根據這個ID集合進行一些特殊處理。

當兩個及以上的手指觸摸屏幕時,會產生多點觸摸事件傳遞給ViewGroup,該MotionEvent中除了會存儲事件類型和座標位置等信息外,還會保存一組觸摸點信息。當觸摸點落於ViewGroup中的不同child上時,需要對MotionEvent進行事件拆分,再將拆分後的事件派發給對應child。

一次完整的派發事件序列是從ACTION_DOWN開始,ACTION_UP/ACTION_CANCEL結束,當中間出現ACTION_POINTER_DOWNACTION_POINTER_UP時,說明產生觸摸點數量變動。

源碼探究

文中源碼基於Android 9.0

結合ViewGroup事件派發流程來看看事件拆分在其中的作用場景,首先分析下代表觸摸事件的類MotionEvent。

MotionEvent說明

ViewGroup在事件派發前,會先從 MotionEvent中獲取中獲取action。該action爲int型,高8位存儲觸摸點索引集合,低8位纔是存儲動作類型(ACTION_DOWN時索引都是0)。當處於多點觸摸情況下,需要通過索引集合中的索引找到觸摸點信息,再從觸摸點信息中獲取觸摸點ID。

例如當第二個觸摸點落於ViewGroup時,此時傳遞進來的 MotionEvent的action低8位是ACTION_POINTER_DOWN,高8位是該觸摸點的索引。同時 MotionEvent中會攜帶當前ViewGroup上的所有觸摸點信息集合。

獲取觸摸點索引

-> MotionEvent.java

public static final int ACTION_POINTER_INDEX_MASK  = 0xff00;
public static final int ACTION_POINTER_INDEX_SHIFT = 8;

// Pointer to the native MotionEvent object that contains the actual data.
private long mNativePtr;

public final int getActionIndex() {
    // 從native層獲取該MotionEvent對應Action值,取高8位值後右移,得到索引值。
    return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)
            >> ACTION_POINTER_INDEX_SHIFT;
}

mNativePtr成員爲指向包含實際數據的native層MotionEvent對象的指針。

-> android_view_MotionEvent.cpp

static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {
    // 通過java層保存的指針類型轉換獲得native層MotionEvent對象,詳細數據都在這裏面。
    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
    return event->getAction();
}

獲取指定觸摸點ID

在得到觸摸點索引後,即可通過索引來獲取觸摸點ID。

-> MotionEvent.java

public final int getPointerId(int pointerIndex) {
    // 也是通過native方法獲取
    return nativeGetPointerId(mNativePtr, pointerIndex);
}

-> android_view_MotionEvent.cpp

static jint android_view_MotionEvent_nativeGetPointerId(JNIEnv* env, jclass clazz,
        jlong nativePtr, jint pointerIndex) {
    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
    // 獲取MotionEvent中存儲的觸摸點個數
    size_t pointerCount = event->getPointerCount();
    // 檢查索引是否越界
    if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
        return -1;
    }
    // 獲取索引對應的觸摸點ID
    return event->getPointerId(pointerIndex);
}

獲取所有觸摸點ID

獲取該MotionEvent中包含的所有觸摸點ID,保存在一個int中。

-> MotionEvent.java

public final int getPointerIdBits() {
    int idBits = 0;
    // 獲取觸摸點個數
    final int pointerCount = nativeGetPointerCount(mNativePtr);
    for (int i = 0; i < pointerCount; i++) {
        // 依次用索引獲取ID,通過|=操作合併在一個int上。
        idBits |= 1 << nativeGetPointerId(mNativePtr, i);
    }
    return idBits;
}

獲取指定觸摸點位置座標

根據觸摸點索引獲取對應觸摸點的位置座標,以獲取X座標爲例:

-> MotionEvent.java

private static final int HISTORY_CURRENT = -0x80000000;

public final float getX(int pointerIndex) {
    return nativeGetAxisValue(mNativePtr, AXIS_X, pointerIndex, HISTORY_CURRENT);
}

-> android_view_MotionEvent.cpp

static const jint HISTORY_CURRENT = -0x80000000;

static jfloat android_view_MotionEvent_nativeGetAxisValue(JNIEnv* env, jclass clazz,
        jlong nativePtr, jint axis, jint pointerIndex, jint historyPos) {
    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
    size_t pointerCount = event->getPointerCount();
    // 索引值校驗
    if (!validatePointerIndex(env, pointerIndex, pointerCount)) {
        return 0;
    }

    // 默認相同
    if (historyPos == HISTORY_CURRENT) {
        // 根據座標類型和觸摸點索引獲取值
        return event->getAxisValue(axis, pointerIndex);
    } else {
        size_t historySize = event->getHistorySize();
        if (!validateHistoryPos(env, historyPos, historySize)) {
            return 0;
        }
        return event->getHistoricalAxisValue(axis, pointerIndex, historyPos);
    }
}

拆分事件

根據給定的ID集合分離事件(該方法後面再詳細分析)。

-> MotionEvent.java

public final MotionEvent split(int idBits) {}

實際MotionEvent數據

通過前面幾個方法看到,Java層MotionEvent獲取數據都是通過jni向native層的MotionEvent查詢數據。

native層MotionEvent定義在:

-> Input.h

class MotionEvent : public InputEvent {
public:
    // ···
    inline int32_t getPointerId(size_t pointerIndex) const {
        // 從mPointerProperties數組獲取對應索引的觸摸點信息
        return mPointerProperties[pointerIndex].id;
    }

protected:
    int32_t mAction;
    int32_t mActionButton;
    int32_t mFlags;
    int32_t mEdgeFlags;
    int32_t mMetaState;
    int32_t mButtonState;
    float mXOffset;
    float mYOffset;
    float mXPrecision;
    float mYPrecision;
    nsecs_t mDownTime;
    // 存儲觸摸點ID信息
    Vector<PointerProperties> mPointerProperties;
    Vector<nsecs_t> mSampleEventTimes;
    // 存儲觸摸點座標信息
    Vector<PointerCoords> mSamplePointerCoords;
};

MotionEvent中持有一個PointerProperties數組,保存着這個事件中包含的所有觸摸點信息,一個PointerProperties結構體對應着一個觸摸點信息,PointerProperties中的id成員即表示觸摸點ID。觸摸點ID的取值是從0開始,依次遞增,最多不超過31。

觸摸點索引和ID關係

在這裏插入圖片描述

觸摸點按下擡起時產生的事件中的觸摸點信息中的索引和ID關係如圖所示,其中索引值是會相對變化的,而ID值保持不變。

派發過程

接下來進入ViewGroup的事件派發方法。

派發目標查找

進入dispatchTouchEvent方法,派發目標確認部分:
-> ViewGroup.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    // ···
    
    // Update list of touch targets for pointer down, if needed.
    // 標記當前ViewGroup是否啓用了事件拆分
    final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    if (!canceled && !intercepted) {
        // ···

        // split && actionMasked == MotionEvent.ACTION_POINTER_DOWN說明有新的觸摸點產生。
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            final int actionIndex = ev.getActionIndex(); // always 0 for down
            // 通過觸摸點索引獲取觸摸點ID,並將ID值保存在一個int上面,通過第x位爲1來表示(x=ID)。
            final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                    : TouchTarget.ALL_POINTER_IDS;

            // Clean up earlier touch targets for this pointer id in case they
            // have become out of sync.
            removePointersFromTouchTargets(idBitsToAssign);

            final int childrenCount = mChildrenCount;
            if (newTouchTarget == null && childrenCount != 0) {
                // 獲取觸摸點位置座標
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(actionIndex);
                // Find a child that can receive the event.
                // Scan children from front to back.
                final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                final boolean customOrder = preorderedList == null
                        && isChildrenDrawingOrderEnabled();
                final View[] children = mChildren;
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final int childIndex = getAndVerifyPreorderedIndex(
                            childrenCount, i, customOrder);
                    final View child = getAndVerifyPreorderedView(
                            preorderedList, children, childIndex);

                    // ···

                    if (!canViewReceivePointerEvents(child)
                            || !isTransformedTouchPointInView(x, y, child, null)) {
                        ev.setTargetAccessibilityFocus(false);
                        continue;
                    }

                    newTouchTarget = getTouchTarget(child);
                    if (newTouchTarget != null) {
                        // Child is already receiving touch within its bounds.
                        // Give it the new pointer in addition to the ones it is handling.
                        // 找到一個派發目標,給這個目標添加新的觸摸點ID。
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                        break;
                    }

                    resetCancelNextUpFlag(child);
                    // dispatchTransformedTouchEvent方法派發,第四個參數傳入上面
                    // 獲取的觸摸點ID,事件拆分在該方法中執行。
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        // ···
                        
                        // 若該child消費了事件,則新建TouchTarget保存child和觸摸點ID,並添入TouchTarget鏈表。
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }

                    // ···
                }
                if (preorderedList != null) preorderedList.clear();
            }

            if (newTouchTarget == null && mFirstTouchTarget != null) {
                // Did not find a child to receive the event.
                // Assign the pointer to the least recently added target.
                newTouchTarget = mFirstTouchTarget;
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                }
                // 若決定派發給最早添加的TouchTarget的話,則往它添加觸摸點ID。
                newTouchTarget.pointerIdBits |= idBitsToAssign;
            }
        }
    }
    
    // ···
}

在派發目標查找階段,若當次事件爲ACTION_DOWN或ACTION_POINTER_DOWN,說明有新觸摸點產生,則會獲取該事件對應的觸摸點ID,然後將ID添加至確定派發的TouchTarget中

執行派發

-> ViewGroup.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    // ···
    
    // Dispatch to touch targets.
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        
        // 常量ALL_POINTER_IDS值爲-1,所有bit位都爲1。
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        // Dispatch to touch targets, excluding the new touch target if we already
        // dispatched to it.  Cancel touch targets if necessary.
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else {
                final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                // 這裏dispatchTransformedTouchEvent第四個參數傳入各個
                // TouchTarget中保存的觸摸點ID集合。
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                    handled = true;
                }
                if (cancelChild) {
                    // ···
                    continue;
                }
            }
            predecessor = target;
            target = next;
        }
    }
    
    // ···
}

在遍歷TouchTarget鏈表依次派發過程中,會取出各個TouchTarget中保存的觸摸點ID集合,表示該目標對這些觸摸點上的事件感興趣。這裏將ID集合傳入dispatchTransformedTouchEvent方法,在該方法中會根據ID集合對事件進行拆分。

dispatchTransformedTouchEvent

-> ViewGroup.java

// 參數desiredPointerIdBits表示child期望接收哪些觸摸點上的事件
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
    final boolean handled;

    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    final int oldAction = event.getAction();
    // 判斷是否需要取消事件序列,若是的話則派發ACTION_CANCEL事件。
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        // 沒有派發目標的情況下,child爲null,交由ViewGroup自身處理。
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    // Calculate the number of pointers to deliver.
    // 獲取該事件上所有的觸摸點ID
    final int oldPointerIdBits = event.getPointerIdBits();
    // 和期望接收的觸摸點做相與操作得到新的觸摸點集合。正常情況下newPointerIdBits就是
    // desiredPointerIdBits,這裏做這樣操作的目的是一種校驗目的。
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    // If for some reason we ended up in an inconsistent state where it looks like we
    // might produce a motion event with no pointers in it, then drop the event.
    // 由於某些異常原因導致desiredPointerIdBits不存在於oldPointerIdBits,出現
    // newPointerIdBits爲0。此種情況下沒有找到有效觸摸點,則丟棄該事件。
    if (newPointerIdBits == 0) {
        return false;
    }

    // If the number of pointers is the same and we don't need to perform any fancy
    // irreversible transformations, then we can reuse the motion event for this
    // dispatch as long as we are careful to revert any changes we make.
    // Otherwise we need to make a copy.
    
    // transformedEvent用於保存事件副本
    final MotionEvent transformedEvent;
    // 判斷觸摸點是否產生變化,例如有新的觸摸點按下或舊觸摸點擡起。
    if (newPointerIdBits == oldPointerIdBits) {
        // 觸摸點ID集合無變化,則不需要進行事件拆分。
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                // 座標系偏移以適應子view座標系
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);

                // 派發給child
                handled = child.dispatchTouchEvent(event);

                // 恢復座標偏移
                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        // 若child需要計算變化矩陣,這裏獲取一個事件副本
        transformedEvent = MotionEvent.obtain(event);
    } else {
        // 觸摸點ID有變化,進行事件拆分,保存拆分事件副本
        transformedEvent = event.split(newPointerIdBits);
    }

    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }

        // 使用事件副本進行派發給child
        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

當MotionEvent中的觸摸點ID集合和當前即將進行派發的TouchTarget中的ID集合完全一致時,就不需要進行事件拆分。否則會根據TouchTarget中的ID集合從MotionEvent中拆分出僅包含TouchTarget期望處理的觸摸點的事件的副本,將事件副本派發給該TouchTarget。

事件拆分規則

拆分規則在MotionEvent的split方法中:

-> MotionEvent.java

// 參數idBits表示TouchTarget感興趣的那些觸摸點(即落於TouchTarget中的觸摸點),
// 期望拆分出僅包含這些觸摸點的事件
public final MotionEvent split(int idBits) {
    // 從對象緩存池獲取一個MotionEvent作爲副本
    MotionEvent ev = obtain();
    synchronized (gSharedTempLock) {
        // 獲取該事件中的觸摸點個數
        final int oldPointerCount = nativeGetPointerCount(mNativePtr);
        // 初始化gSharedTempPointerProperties、gSharedTempPointerCoords、gSharedTempPointerIndexMap數組。
        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;
        // 若當前按下或擡起的那個觸摸點是TouchTarget感興趣的,則單獨記錄這個觸摸點索引
        int newActionPointerIndex = -1;
        // TouchTarget感興趣的觸摸點個數
        int newPointerCount = 0;
        // TouchTarget感興趣的觸摸點ID集合
        int newIdBits = 0;
        // 遍歷MotionEvent中攜帶的所有觸摸點信息
        for (int i = 0; i < oldPointerCount; i++) {
            // 將native層中的信息保存至Java層
            nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
            // 獲取觸摸點ID
            final int idBit = 1 << pp[newPointerCount].id;
            if ((idBit & idBits) != 0) {
                // 該觸摸點是TouchTarget感興趣的
                if (i == oldActionPointerIndex) {
                    // 且該觸摸點是引發當前事件的那個觸摸點,特別記錄下它的索引
                    newActionPointerIndex = newPointerCount;
                }
                // 緩存記錄
                map[newPointerCount] = i;
                newPointerCount += 1;
                newIdBits |= idBit;
            }
        }

        // 安全檢查
        if (newPointerCount == 0) {
            throw new IllegalArgumentException("idBits did not match any ids in the event");
        }

        // 用於記錄事件拆分後新的動作類型
        final int newAction;
        // 僅對ACTION_POINTER_DOWN和ACTION_POINTER_UP進行類型調整
        if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
            if (newActionPointerIndex < 0) {
                // An unrelated pointer changed.
                // 引發當前事件的那個觸摸點不是TouchTarget感興趣的,則將類型調整爲
                // ACTION_MOVE,對於該TouchTarget來說,當作普通的滑動事件處理。
                newAction = ACTION_MOVE;
            } else if (newPointerCount == 1) {
                // The first/last pointer went down/up.
                // 引發當前事件的那個觸摸點是該TouchTarget感興趣的,且TouchTarget
                // 感興趣的個數爲1。說明該TouchTarget僅對當前這一個觸摸點感興趣(單點觸摸),那麼
                // 對於該TouchTarget來說,將是一個全新序列的開始或結束。
                // 將動作類型調整爲ACTION_DOWN或ACTION_UP。
                newAction = oldActionMasked == ACTION_POINTER_DOWN
                        ? ACTION_DOWN : ACTION_UP;
            } else {
                // A secondary pointer went down/up.
                // 到了這個case,意味着該觸摸點是該TouchTarget上的多點觸摸事件,沿用
                // 動作類型,並組合上觸摸點索引。
                newAction = oldActionMasked
                        | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
            }
        } else {
            // Simple up/down/cancel/move or other motion action.
            newAction = oldAction;
        } // 事件動作類型調整完畢

        // 初始化MotionEvent副本
        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) {
                // 使用原對象數據初始化native層對象,並返回對象指針,這裏傳入了調整後的動作類型。
                ev.mNativePtr = nativeInitialize(ev.mNativePtr,
                        nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
                        newAction, nativeGetFlags(mNativePtr),
                        nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
                        nativeGetButtonState(mNativePtr),
                        nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
                        nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
                        nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
                        newPointerCount, pp, pc);
            } else {
                nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
            }
        }
        return ev;
    }
}

split方法中主要根據傳入的idBits調整事件的Action,這麼做的原因是什麼呢?這裏以一個圖示爲例:

  • 當觸摸點3按下時,ViewGroup會收到ACTION_POINTER_DOWN事件,該觸摸點是child B感興趣的。此時對於child B來說是一個全新的事件序列開始,因此在派發給child B時,需要將類型調整爲ACTION_DOWN。但是對於child A來說,並不是它感興趣的,因此在派發給child A時要調整爲ACTION_MOVE。
  • 當觸摸點2擡起時,ViewGroup會收到ACTION_POINTER_UP事件。該事件是child A感興趣的,但是child A上仍有觸摸點1,因此派發給child A的事件類型依舊是ACTION_POINTER_UP。而在派發給child B時,將調整爲ACTION_MOVE。

觸摸點ID的移除

在派發流程的末尾,當判斷有觸摸點擡起時,會移除相應的觸摸點ID:
-> ViewGroup.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    // ···
    
    // Update list of touch targets for pointer up or cancel, if needed.
    if (canceled
            || actionMasked == MotionEvent.ACTION_UP
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        resetTouchState();
    } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
        // 事件類型爲ACTION_POINTER_UP
        final int actionIndex = ev.getActionIndex();
        // 通過索引獲取引發當前事件的觸摸點ID
        final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
        // 移除該觸摸點ID
        removePointersFromTouchTargets(idBitsToRemove);
    }
    
    // ···
}

接着看removePointersFromTouchTargets方法:
-> ViewGroup.java

// 參數pointerIdBits爲將被移除的觸摸點ID
private void removePointersFromTouchTargets(int pointerIdBits) {
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    // 遍歷TouchTarget鏈表
    while (target != null) {
        final TouchTarget next = target.next;
        if ((target.pointerIdBits & pointerIdBits) != 0) {
            // 若該TouchTarget上有該ID,則從中移除ID
            target.pointerIdBits &= ~pointerIdBits;
            if (target.pointerIdBits == 0) {
                // 若TouchTarget移除ID後,沒有任何ID了,則從鏈表中移除該TouchTarget
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

總結

事件拆分是爲了在多點觸摸情況下更準確的將事件傳遞給子view,在派發流程中,ViewGroup不會原樣把MotionEvent派發給子view,而是根據落於子view上的觸摸點,調整MotionEvent中的事件類型和觸摸點信息後生成新的MotionEvent副本,再用這個MotionEvent副本派發給對應子view。

發佈了27 篇原創文章 · 獲贊 2 · 訪問量 8504
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章