Android:事件分發(fā)機(jī)制源碼解讀與滑動(dòng)沖突解決方案

事件分發(fā)源代碼分析

1. Activity 事件分發(fā)

首先從 Activity 的 dispatchTouchEvent 方法入手

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

可以看出,Activity 其實(shí)是調(diào)用了 Window 的 superDispatchTouchEvent 方法,而 Window 的實(shí)現(xiàn)類是 PhoneWindow,因此我們直接查看 PhoneWindow 的 superDispatchTouchEvent 方法

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

發(fā)現(xiàn)是直接調(diào)用的 DecorView 的 superDispatchTouchEvent 方法,再進(jìn)一步查看

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

原來這兒就調(diào)用了 ViewGroup 的 dispatchTouchEvent 方法,也就是說界面上的事件直接傳遞給了根布局的 dispatchTouchEvent 方法

2. ViewGroup 事件分發(fā)

在講 ViewGroup 的 dispatchTouchEvent 方法之前,我們先看看 ViewGroup 的 dispatchTransformedTouchEvent 方法,dispatchTouchEvent 內(nèi)部多次調(diào)用了 dispatchTransformedTouchEvent 方法。因?yàn)榇a量較多,這里只提取我們關(guān)心的部分

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;

    // 如果是取消操作,則直接分發(fā)取消事件
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        // 如果傳入的 child 不為空,則調(diào)用 child 的 dispatchTouchEvent 方法,否則調(diào)用自身的 dispatchTouchEvent 方法
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    ......

    // 如果傳入的 child 不為空,則調(diào)用 child 的 dispatchTouchEvent 方法,否則調(diào)用自身的 dispatchTouchEvent 方法
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        ......
        handled = child.dispatchTouchEvent(transformedEvent);
    }

    ......
    return handled;
}

可以看出 dispatchTransformedTouchEvent 方法主要做了兩件事

  • 如果傳入的事件是 ACTION_CANCEL,或者 cancel 參數(shù)為 true,則直接分發(fā) ACTION_CANCEL 事件
  • 分發(fā)過程中,如果 child 為空,則調(diào)用當(dāng)前 View 的 super.dispatchTouchEvent 方法,這是因?yàn)?ViewGroup 的 dispatchTouchEvent 方法會被重寫,而此時(shí)調(diào)用 super 的方法也就是調(diào)用 View 的 dispatchTouchEvent 方法;如果 child 不為空,則調(diào)用這個(gè)子 View 的 dispatchTouchEvent 方法。

然后我們再來看看 dispatchTouchEvent 方法,同樣代碼量特別多,我們只抽取我們關(guān)心的,即使這樣代碼量依然很多。我們先列出來,不用仔細(xì)看,后面會分塊拆分講解

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ......
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 1. DOWN 事件進(jìn)行初始化,清空 TouchTargets 和 TouchState
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 2. 檢查是否攔截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 是否強(qiáng)制不允許攔截,子 View 可以設(shè)置 parent 強(qiáng)制不允許攔截,默認(rèn)為 false
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }

        ......
        // 3. 如果沒有被攔截, 先處理 DOWN 事件,主要是賦值 TouchTarget
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {
            ......
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                ......
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    ......
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
                        ......
                        // 找到 Visible 并且處于點(diǎn)擊范圍的子 View
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        ......
                        // 相當(dāng)于調(diào)用子 View 的 dispatchTouchEvent 方法
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            ......
                            // 賦值 TouchTarget,刷新標(biāo)志位
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ......
                    }
                    ......
                }
                ......
            }
        }

        // 4. 是自己處理事件還是交由子 View 處理事件
        if (mFirstTouchTarget == null) {
            // 沒有子 View 消耗事件,則自己消耗,相當(dāng)于調(diào)用 super.dispatchTouchEvent 方法
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                // 如果是 DOWN 事件,則上面已經(jīng)調(diào)用了子 View 的 dispatchTouchEvent 方法,則什么都不用做
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    // 根據(jù) intercepted 決定是否將事件強(qiáng)制改為 CANCEL 事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    // 相當(dāng)于調(diào)用子 View 的 dispatchTouchEvent 方法。如果 intercepted=true,此時(shí)會強(qiáng)制將 action 改為 CANCEL;如果 intercepted=false,則
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    // 如果 intercepted=true,則將 mFirstTouchTarget 置為 null
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }
        ......
    }
    ......
    return handled;
}

dispatchTouchEvent 方法主要由4個(gè)模塊組成的

  1. DOWN 事件進(jìn)行初始化,清空 TouchTargets 和 TouchState
  2. 檢查是否攔截
  3. 如果沒有被攔截, 先處理 DOWN 事件,主要是賦值 TouchTarget
  4. 是自己處理事件還是交由子 View 處理事件

A. 第一步:DOWN 事件時(shí)進(jìn)行初始化

// 1. DOWN 事件進(jìn)行初始化,清空 TouchTargets 和 TouchState
if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

我們知道,一個(gè)事件序列是由一個(gè) ACTION_DOWN,零個(gè)或多個(gè) ACTION_MOVE 和一個(gè) ACTION_UP 組成的。ACTION_DOWN 是一個(gè)事件序列的開始,ACTION_UP 是一個(gè)事件序列的結(jié)束。在 ACTION_DOWN 時(shí),需要對一些狀態(tài)進(jìn)行初始化和重置。上面的 cancelAndClearTouchTargets 和 resetTouchState 方法,都是初始化一些狀態(tài),最重要的我們關(guān)心的就是內(nèi)部調(diào)用了這個(gè)初始化代碼

private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
    }
}

可以看出來這里是用循環(huán)的方式把單鏈表 mFirstTouchTarget 給清空了,至于 mFirstTouchTarget 是什么這里先不講,只需要知道它保存了一個(gè)指定消耗事件的子 View 便可,后續(xù)的所有事件會直接交給這個(gè) View 消耗

B. 第二步:檢查是否攔截

// 2. 檢查是否攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    // 是否強(qiáng)制不允許攔截,子 View 可以設(shè)置 parent 強(qiáng)制不允許攔截,默認(rèn)為 false
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    intercepted = true;
}

這里我們可以看出,如果是 ACTION_DOWN 事件,或者 mFirstTouchTarget 不為空的時(shí)候,就會進(jìn)入是否攔截的邏輯判斷。這里有一個(gè) disallowIntercept 的判斷,這個(gè)標(biāo)記是子 View 設(shè)置 ViewGroup 是否允許攔截的情況,這里我們暫不用理解這種情況,默認(rèn) disallowIntercept 為 false,也就是允許攔截。此時(shí)會判斷 onInterceptTouchEvent 方法,而 onInterceptTouchEvent 方法默認(rèn)返回 false。

而 mFirstTouchTarget 是怎么來的呢?在后面會有詳細(xì)的講解,這里先簡單的說明一下:

  • 如果 intercepted 為 false,并且在后面有一個(gè)子 View 的 dispatchTouchEvent 方法在 ACTION_DOWN 時(shí)返回了 true,那么就會對 mFirstTouchTarget 進(jìn)行賦值;
  • 如果 intercepted 為 true,那么在后面就會對 mFirstTouchTarget 置為 null

我們來走一遍所有可能的路徑:

  1. 事件序列開始,也就是說,在 ACTION_DOWN 時(shí),會調(diào)用 onInterceptTouchEvent 判斷是否攔截;
  2. 如果此時(shí) onInterceptTouchEvent 返回 true,intercepted 賦值為 true,則后面 mFirstTouchTarget 不會再被賦值,mFirstTouchTarget 會一直為 null,后續(xù)的 ACTION_MOVE 和 ACTION_UP 事件 onInterceptTouchEvent 方法不會再被調(diào)用,intercepted 會一直為 true
  3. 如果此時(shí) onInterceptTouchEvent 返回 false,intercepted 賦值為 false,后面如果有子 View 的 dispatchTouchEvent 方法返回了 true,mFirstTouchTarget 會被賦值,后續(xù)的 ACTION_MOVE 和 ACTION_UP 事件每次都會調(diào)用 onInterceptTouchEvent 方法
  4. 在后續(xù)的 ACTION_MOVE 和 ACTION_UP 事件中,如果 onInterceptTouchEvent 返回了 true,那么在后面會清空 mFirstTouchTarget 的值,那么在下一個(gè)事件直到最后一個(gè)事件,onInterceptTouchEvent 方法又不會被調(diào)用了

總結(jié)來說,onInterceptTouchEvent 一旦某一次返回了 true,那么后面的事件都不會再調(diào)用 onInterceptTouchEvent 進(jìn)行是否攔截的判斷,intercepted 的值會一直為 true

C. 第三步:先處理 DOWN 事件

// 3. 如果沒有被攔截, 先處理 DOWN 事件,主要是賦值 TouchTarget
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
    ......
    if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        ......
        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
            ......
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = getAndVerifyPreorderedIndex(
                        childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(
                        preorderedList, children, childIndex);
                ......
                // 找到 Visible 并且處于點(diǎn)擊范圍的子 View
                if (!canViewReceivePointerEvents(child)
                        || !isTransformedTouchPointInView(x, y, child, null)) {
                    ev.setTargetAccessibilityFocus(false);
                    continue;
                }
                ......
                // 相當(dāng)于調(diào)用子 View 的 dispatchTouchEvent 方法
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    ......
                    // 賦值 TouchTarget,刷新標(biāo)志位
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
                ......
            }
            ......
        }
        ......
    }
}

這段代碼的前提條件就是 intercepted 為 false,并且是 ACTION_DOWN 事件,此時(shí)會遍歷每一個(gè)滿足條件(處于Visible狀態(tài)并且處于點(diǎn)擊范圍內(nèi))的子 View,調(diào)用了 dispatchTransformedTouchEvent 方法,dispatchTransformedTouchEvent 方法最開始我們就講解過,此時(shí)的 child 參數(shù)不為空,所以就是調(diào)用的 child 的 dispatchTouchEvent 方法。如果 dispatchTransformedTouchEvent 返回為 true,則調(diào)用 addTouchTarget 方法對 mFirstTouchTarget 進(jìn)行賦值,并且將變量 alreadyDispatchedToNewTouchTarget 置為 true,然后緊跟了一個(gè) break 跳出循環(huán),為什么要跳出循環(huán)呢?這是因?yàn)橐粋€(gè)事件只能被一個(gè) View 消耗(dispatchTouchEvent 返回 true 代表消耗)。

我們看看 addTouchTarget 方法

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

就是一個(gè)單鏈表操作,新增一個(gè) TouchTarget 并插入到表頭,然后將表頭賦值給 mFirstTouchTarget。

總結(jié)來說,ACTION_DOWN 時(shí),如果 intercepted 為 true,則不會有任何子 View 調(diào)用 dispatchTouchEvent 方法,并且 mFirstTouchTarget 不會被賦值,會一直為 null;如果 intercepted 為 false,那么在滿足條件的某個(gè)子 View 的 dispatchTouchEvent 方法返回 true 時(shí),mFirstTouchTarget 也會被賦值

D. 第四步:分發(fā)事件

// 4. 是自己處理事件還是交由子 View 處理事件
if (mFirstTouchTarget == null) {
    // 沒有子 View 消耗事件,則自己消耗,相當(dāng)于調(diào)用 super.dispatchTouchEvent 方法
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        // 如果是 DOWN 事件,則上面已經(jīng)調(diào)用了子 View 的 dispatchTouchEvent 方法,則什么都不用做
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            // 根據(jù) intercepted 決定是否將事件強(qiáng)制改為 CANCEL 事件
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
            // 相當(dāng)于調(diào)用子 View 的 dispatchTouchEvent 方法。如果 intercepted=true,此時(shí)會強(qiáng)制將 action 改為 CANCEL;如果 intercepted=false,則
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true;
            }
            // 如果 intercepted=true,則將 mFirstTouchTarget 置為 null
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

可以看到,只要 mFirstTouchTarget 為 null,那么就會直接調(diào)用 dispatchTransformedTouchEvent 方法,child 參數(shù)為 null,那么就會調(diào)用當(dāng)前 ViewGroup 的 super.dispatchTouchEvent 方法。

如果 mFirstTouchTarget 不為 null,首先判斷 alreadyDispatchedToNewTouchTarget 和 newTouchTarget 變量決定是否直接返回。由上面第3步可知,如果 alreadyDispatchedToNewTouchTarget 為 true,則代表當(dāng)前是 ACTION_DOWN 事件,并且找到了一個(gè)子 View 并且調(diào)用過了 child 的 dispatchTouchEvent 方法,因此這里什么都不用做。

然后再看看 cancelChild 變量,它是由 intercepted 決定的。假如 cancelChild 為 false,說明 intercepted 一定為 false,然后因?yàn)?mFirstTouchTarget 不為空,那么調(diào)用 dispatchTransformedTouchEvent 時(shí) child 不為空,則直接調(diào)用 child 的 dispatchTouchEvent 方法;如果 intercepted 為 true,那么 cancelChild 一定為 true,dispatchTransformedTouchEvent 的 cancel 參數(shù)為 true,那么會直接給 mFirstTouchTarget 對應(yīng)的 child 傳遞一個(gè) ACTION_CANCEL 事件,然后在 mFirstTouchTarget 對應(yīng)的單鏈表中,刪除 mFirstTouchTarget 當(dāng)前對應(yīng)的 TouchTarget

總結(jié)一下,如果 mFirstTouchTarget 為 null,則調(diào)用自身的 super.dispatchTouchEvent 方法;如果 mFirstTouchTarget 不為 null,如果上面已經(jīng)處理過 ACTION_DOWN 事件,則什么都不做;如果 intercepted 為 false,則直接調(diào)用 mFirstTouchTarget 對應(yīng)的 child 的 dispatchTouchEvent方法;如果 intercepted 為 true,則會強(qiáng)制給 mFirstTouchTarget 對應(yīng)的 child 分發(fā)一個(gè) ACTION_CANCEL 事件,然后將 mFirstTouchTarget 置為 null

E. ViewGroup 的 dispatchTouchEvent 事件分發(fā)流程

我們將一個(gè)完整的事件序列的整體流程走一遍

  1. 當(dāng) ACTION_DOWN 事件時(shí),會將 mFirstTouchTarget 置為 null
  2. 然后判斷 onInterceptTouchEvent 是否攔截,如果返回 true,則后續(xù)事件都不會再調(diào)用 onInterceptTouchEvent 方法來判斷是否攔截并且默認(rèn)都是 true
  3. 因?yàn)?intercepted 為 true,并且 mFirstTouchTarget 為 null,因此會直接調(diào)用自身的 super.dispatchTouchEvent 方法。并且后續(xù)事件都會如此執(zhí)行(若下一個(gè)事件能到達(dá))。
  4. 如果第2步的 onInterceptTouchEvent 返回 false,則會嘗試找到一個(gè)滿足條件的子 View,分發(fā)此次 ACTION_DOWN 事件,并對 mFirstTouchTarget 進(jìn)行賦值
  5. 如果第4步找不到合適的子 View,mFirstTouchTarget 依然為 null,則會調(diào)用自身的 super.dispatchTouchEvent 方法。并且后續(xù)事件都會如此執(zhí)行(若下一個(gè)事件能到達(dá))。
  6. 如果第4步找到了合適的子 View,也就意味著分發(fā)了一個(gè) ACTION_DOWN 事件,那么 ACTION_DOWN 就什么都不用做了。
  7. 當(dāng) ACTION_MOVE 和 ACTION_UP 事件時(shí),如果第4步找到了合適的子 View,mFirstTouchTarget 被賦值,那么會再次判斷 onInterceptTouchEvent 是否攔截。
  8. 如果第7步判斷為不攔截,則直接分發(fā)此次事件給 mFirstTouchTarget 對應(yīng)的 child
  9. 如果第7步判斷為攔截,則強(qiáng)制分發(fā) ACTION_CANCEL 事件給 mFirstTouchTarget 對應(yīng)的 child,然后將 mFirstTouchTarget 置為 null

3. View事件分發(fā)

public boolean dispatchTouchEvent(MotionEvent event) {
    ......
    if (onFilterTouchEventForSecurity(event)) {
        ......
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ......
    return result;
}

看完了 ViewGroup 的 dispatchTouchEvent,再來看 View 的 dispatchTouchEvent 就發(fā)現(xiàn)是不是太簡單了。這里簡單提醒一下:在 ViewGroup 調(diào)用 super.dispatchTouchEvent 其實(shí)就是調(diào)用的 View 的 dispatchTouchEvent 方法。

首先,如果設(shè)置了 mOnTouchListener,則會優(yōu)先處理 mOnTouchListener 的 onTouch 方法,如果 onTouch 方法返回 true,則此方法直接返回而不會再調(diào)用 onTouchEvent 方法了。也就是說 mOnTouchListener 的優(yōu)先級高于 onTouchEvent 方法。如果在 mOnTouchListener 的 onTouch 方法中返回了 true,也許會因?yàn)闆]有執(zhí)行 onTouchEvent 方法而導(dǎo)致點(diǎn)擊等回調(diào)不被調(diào)用。最后會將 mOnTouchListener 或 onTouchEvent 方法的返回值作為此方法的返回值。

然后我們看看 onTouchEvent 方法

public boolean onTouchEvent(MotionEvent event) {
    ......
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        ......
        return clickable;
    }
    ......

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ......
                boolean focusTaken = false;
                if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                    focusTaken = requestFocus();
                }
                ......
                if (!focusTaken) {
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClickInternal();
                    }
                }
                ......
                break;
            ......
        }
        return true;
    }
    return false;
}

首先,只要可以點(diǎn)擊或者長按,clickable 就為 true。不管當(dāng)前控件是不是 DISABLED,都返回 clickable。clickable 根據(jù)不同的控件而值不同,比如 Button 就為 true,TextView 就為 false。設(shè)置對應(yīng)的點(diǎn)擊監(jiān)聽器會默認(rèn)將 clickable 設(shè)為 true。也就是說,只要當(dāng)前控件是可點(diǎn)擊的,那么 onTouchEvent 默認(rèn)返回 true 而消耗這一串事件序列。

在 ACTION_UP 中,會完成點(diǎn)擊事件的判斷,具體是通過 performClickInternal 方法完成的

private boolean performClickInternal() {
    // Must notify autofill manager before performing the click actions to avoid scenarios where
    // the app has a click listener that changes the state of views the autofill service might
    // be interested on.
    notifyAutofillManagerOnClick();
    return performClick();
}

也就是通過 performClick 方法來完成的

public boolean performClick() {
    // We still need to call this method to handle the cases where performClick() was called
    // externally, instead of through performClickInternal()
    notifyAutofillManagerOnClick();

    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    notifyEnterOrExitForAutoFillIfNeeded(true);
    return result;
}

在這里,顯示觸發(fā) mOnClickListener 的 onClick 方法來完成點(diǎn)擊事件的回調(diào)。

滑動(dòng)沖突解決

根據(jù)以上源代碼分析,我們可以找到一種解決滑動(dòng)沖突的套路:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercept = false;
    float x = ev.getX();
    float y = ev.getY();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            intercept = false;
            break;
        case MotionEvent.ACTION_MOVE:
            // 橫向滑動(dòng)則自己處理,豎向滑動(dòng)則子View處理
            if (Math.abs(x - lastX) > Math.abs(y - lastY)){
                intercept = true;
            } else {
                intercept = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercept = false;
            break;
    }
    lastX = x;
    lastY = y;
    return intercept;
}
  • ACTION_DOWN 事件一定要返回 false,否則所有的事件都會被 ViewGroup 攔截,并且不會再調(diào)用 onInterceptTouchEvent 方法。因此 ACTION_DOWN 事件一定會被子 View 所消耗。如果當(dāng)前 ViewGroup 需要在 ACTION_DOWN 處理一些邏輯,則可以在 dispatchTouchEvent 或者 onInterceptTouchEvent 方法處理
  • ACTION_MOVE 事件根據(jù)需要來決定是否攔截,一旦攔截,則會立馬給 mFirstTouchTarget 指定的 child 分發(fā)一個(gè) ACTION_CANCEL 事件,后續(xù)的事件序列都直接交由 ViewGroup 消耗,并且不會再調(diào)用 onInterceptTouchEvent 方法
  • ACTION_UP 事件的返回值返回 false,如果在之前的事件序列返回過 true,那么 ACTION_UP 返回什么都無所謂,因?yàn)楦静粫粓?zhí)行到;但是如果之前的事件序列沒有返回過 true,那么 ACTION_UP 返回 true,會直接強(qiáng)制替換為 ACTION_CANCEL 分發(fā)給子 View,導(dǎo)致子 View 的點(diǎn)擊事件無法響應(yīng)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容