Android事件分發(fā)機(jī)制

基礎(chǔ)知識(shí)

  • 事件分發(fā)的對(duì)象

事件分發(fā)的對(duì)象是 點(diǎn)擊事件(Touch事件)陶夜,當(dāng)用戶觸摸屏幕(View或者ViewGroup派生控件)時(shí),將會(huì)產(chǎn)生點(diǎn)擊事件(Touch事件)
Touch事件的相關(guān)細(xì)節(jié)寇钉,如觸摸位置攘烛、時(shí)間等被封裝為MotionEvent對(duì)象

  • 事件類型

MotionEvent.ACTION_DOWN----按下View(所有事件的開(kāi)始)
MotionEvent.ACTION_MOVE----滑動(dòng)View
MotionEvent.ACTION_UP----抬起View
MotionEvent.ACTION_CANCEL----結(jié)束事件(所有事件的開(kāi)始)

  • 事件列
image.png

一般,事件列都是以DOWN事件開(kāi)始评抚,UP事件結(jié)束豹缀,中間無(wú)數(shù)個(gè)MOVE事件

  • 事件分發(fā)的本質(zhì)

將點(diǎn)擊事件MotionEvent傳遞到具體某個(gè)View以及處理該事件的整個(gè)過(guò)程

  • 事件分發(fā)在哪些對(duì)象間進(jìn)行傳遞
image.png

Activity----控制生命周期伯复,處理事件
View----所有UI組件的基類
ViewGroup----一組View的集合,本身也是View的子類

  • 事件分發(fā)的順序

Activity -> ViewGroup -> View

  • 事件分發(fā)由哪些主要方法協(xié)作完成

1邢笙、dispatchTouchEvent()

分發(fā)(傳遞)事件
當(dāng)點(diǎn)擊事件能夠傳遞到當(dāng)前View,該方法就會(huì)被調(diào)用

2啸如、onTouchEvent()

處理點(diǎn)擊事件
在dispatchTouchEvent方法內(nèi)部調(diào)用

3、onInterceptTouchEvent()

判斷是否攔截事件
該方法只存在于ViewGroup中氮惯,在ViewGroup的dispatchTouchEvent方法內(nèi)部調(diào)用叮雳。若是重新該方法為true,則表示父容器攔截了事件妇汗,子容器View將無(wú)法處理該事件

源碼流程分析

當(dāng)點(diǎn)擊事件發(fā)生后帘不,事件先傳到Activity、再傳到ViewGroup杨箭、最終傳到View

image.png

從上圖可知寞焙,要充分理解事件分發(fā)機(jī)制,本質(zhì)上是需要理解:
1互婿、Activity對(duì)點(diǎn)擊事件的分發(fā)機(jī)制
2捣郊、ViewGroup對(duì)點(diǎn)擊事件的分發(fā)機(jī)制
3、View對(duì)點(diǎn)擊事件的分發(fā)機(jī)制
下面我們將對(duì)這三個(gè)分發(fā)機(jī)制進(jìn)行逐一分析

  • Activity對(duì)點(diǎn)擊事件的分發(fā)機(jī)制

當(dāng)點(diǎn)擊事件發(fā)生時(shí)慈参,最先響應(yīng)的是Activity的dispatchTouchEvent方法呛牲,那么為什么是它最先響應(yīng)呢?Activity之dispatchTouchEvent前傳

    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 事件都是以按下事件(DOWN)為開(kāi)始驮配,所以此處為true
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction(); // 分析1
        }
        if (getWindow().superDispatchTouchEvent(ev)) { // 分析2
            return true;
        }
        return onTouchEvent(ev); // 分析3
    }
  • 分析1(當(dāng)前在Activity類)
onUserInteraction();
// onUserInteraction方法是一個(gè)空方法娘扩,主要作用是實(shí)現(xiàn)屏保功能
// 當(dāng)此activity在棧頂時(shí),觸屏點(diǎn)擊按home壮锻,back畜侦,recent鍵等都會(huì)觸發(fā)此方法
public void onUserInteraction() {}
  • 分析2(當(dāng)前在Activity類)
getWindow().superDispatchTouchEvent(ev)

// getWindow方法返回 mWindow,它是一個(gè)PhoneWindow對(duì)象
public Window getWindow() {
    return mWindow;
}
private Window mWindow;
// PhoneWindow為Window抽象類的子類
mWindow = new PhoneWindow(this, window, activityConfigCallback);
  • 分析2(當(dāng)前在Window類)
// 抽象方法躯保,由PhoneWindow類實(shí)現(xiàn)
public abstract boolean superDispatchTouchEvent(MotionEvent event);
  • 分析2(當(dāng)前在PhoneWindow類)
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    // 調(diào)用DecorView類中的superDispatchTouchEvent方法
    return mDecor.superDispatchTouchEvent(event);
}
private DecorView mDecor;
  • 分析2(當(dāng)前在DecorView類)
// 調(diào)用父類的dispatchTouchEvent方法
// DecorView是所有UI界面的父類
// DecorView的父類是FrameLayout睬关,但FrameLayout并沒(méi)有實(shí)現(xiàn)dispatchTouchEvent方法
// 所以繼續(xù)向上尋找到FrameLayout的父類专执,即調(diào)用ViewGroup類的dispatchTouchEvent方法
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

此時(shí),事件被分發(fā)到了ViewGroup

  • 分析3(當(dāng)前在Activity類)
return onTouchEvent(ev); 
// 當(dāng)點(diǎn)擊事件未被任何一個(gè)View接收并處理就會(huì)執(zhí)行onTouchEvent方法
// 場(chǎng)景如:發(fā)生在Window邊界外的觸摸事件
public boolean onTouchEvent(MotionEvent event) {
    // 點(diǎn)擊事件發(fā)生在Windows邊界外吭服,返回true
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}
  • 分析3(當(dāng)前在Window類)
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    // 主要是對(duì)于處理邊界外點(diǎn)擊事件的判斷:是否是DOWN事件擅羞,event的坐標(biāo)是否在邊界內(nèi)等
    final boolean isOutside =
        event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
            || event.getAction() == MotionEvent.ACTION_OUTSIDE;
        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
            // 說(shuō)明事件在邊界外尸变,即 消費(fèi)事件
            return true;
        }
        // 未消費(fèi)(默認(rèn))
        return false;
    }
  • Activity對(duì)點(diǎn)擊事件的分發(fā)機(jī)制總結(jié)
image.png

至此,流程已經(jīng)走到了ViewGroup類中的dispatchTouchEvent方法减俏,繼續(xù)分析~

  • ViewGroup對(duì)點(diǎn)擊事件的分發(fā)機(jī)制
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 這是一個(gè)驗(yàn)證事件完整性的一個(gè)類,一會(huì)會(huì)看到在本方法結(jié)束的地方也有這個(gè)類出現(xiàn)
        // 防止event事件在分發(fā)過(guò)程中不一致,這個(gè)類還有記錄的功能
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        // 檢測(cè)無(wú)障礙焦點(diǎn)
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {// 分析1
            // 獲取Touch Action
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) { // 分析2
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            // 判斷事件是否需要攔截
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                // 判斷是否運(yùn)行不允許攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                // 如果允許攔截
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev); // 分析3
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {// 分析4

                // If the event is targeting accessibility focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                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
                    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--) { // 分析8
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            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.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        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;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {// 分析5
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {// 分析6
                // 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;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // 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) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }
  • 分析1(當(dāng)前在View類)
// 檢測(cè)是否分發(fā)Touch事件(判斷窗口是否被遮擋渍倮谩)
// 如果該 Touch 事件沒(méi)有被窗口遮擋,則繼續(xù)后續(xù)邏輯
// 一般情況下都會(huì)返回true
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }
  • 分析2(當(dāng)前在ViewGroup類)
// 判斷當(dāng)前是否是ACTION_DOWN娃承,剛進(jìn)來(lái)的時(shí)候肯定是ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}
private void cancelAndClearTouchTargets(MotionEvent event) {
    ....
    clearTouchTargets();        
    ....
}

// 將mFirstTouchTarget置為null
// 回收TouchTarget對(duì)象
// 清空所有接收觸摸事件View的引用
private void clearTouchTargets() {
    // TouchTarget和Handler中的Message一樣奏夫,都是一個(gè)單向鏈表怕篷,鏈?zhǔn)浇Y(jié)構(gòu),通過(guò)next來(lái)訪問(wèn)下一個(gè)元素
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
    }
}
private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    // 默認(rèn)允許攔截事件
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    // 默認(rèn)視圖不滾動(dòng)
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}
  • 分析3(當(dāng)前在ViewGroup類中)
public boolean onInterceptTouchEvent(MotionEvent ev) {
    // 通常不會(huì)執(zhí)行下面的語(yǔ)句
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    // 默認(rèn)返回false
    return false;
}

通常酗昼,如果ViewGroup或其派生類重寫(xiě)了onInterceptTouchEvent并返回為true廊谓,這說(shuō)明父容器需要攔截當(dāng)前點(diǎn)擊事件,即onInterceptTouchEvent為true麻削。

代碼到了這里蒸痹,出現(xiàn)了一個(gè)分支:
即,如果onInterceptTouchEvent為true呛哟,則執(zhí)行 [分析4]叠荠,否則不執(zhí)行。

接下來(lái)我們先分析父容器攔截事件的情況扫责。

  • 分析5(當(dāng)前在ViewGroup類)
// 此時(shí) mFirstTouchTarget必定為null榛鼎,因?yàn)橹罢{(diào)用cancelAndClearTouchTargets方法時(shí)做了清空處理
// mFirstTouchTarget必定為null,意味著 沒(méi)有任何 view 消費(fèi)該 Touch 事件
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    // ev: event事件類型
    // canceled:是否是cancel公给,因?yàn)闆](méi)有取消借帘,所以當(dāng)前是false
    // null:View child字段傳了null
    // TouchTarget.ALL_POINTER_IDS:位置
    // viewGroup 會(huì)調(diào)用dispatchTransformedTouchEvent處理,但是child==null淌铐,會(huì)調(diào)用View#dispatchTouchEvent處理
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS); // 分析5-1
}
  • 分析5-1(當(dāng)前在ViewGroup類)
    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();
        // cancel為false肺然,下方if語(yǔ)句塊不會(huì)執(zhí)行
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        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.
        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.
        final MotionEvent transformedEvent;
        // 下面if語(yǔ)句塊是一個(gè)新老事件的處理,也不會(huì)進(jìn)入
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            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());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

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

由于child傳進(jìn)來(lái)為null腿准,所以調(diào)用了super.dispatchTouchEvent方法际起,而ViewGroup類的父類是View,所以這里進(jìn)入并執(zhí)行了View類中的dispatchTouchEvent方法吐葱。

當(dāng)前 handler返回為true(邏輯見(jiàn)View的事件分發(fā)機(jī)制流程分析)街望,則回到 [分析5]處, [分析5]的代碼也會(huì)返回true弟跑,則會(huì)回到Activity的dispatchEvent方法中灾前。

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

getWindow().superDispatchTouchEvent(ev)返回為true,至此孟辑,在“父容器攔截事件”的情況下的流程已經(jīng)走完哎甲。

下面分析父容器不攔截子View事件的情況
再次展示VIewGroup的dispatchTouchEvent方法的代碼

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 這是一個(gè)驗(yàn)證事件完整性的一個(gè)類,一會(huì)會(huì)看到在本方法結(jié)束的地方也有這個(gè)類出現(xiàn)
        // 防止event事件在分發(fā)過(guò)程中不一致,這個(gè)類還有記錄的功能
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        // 檢測(cè)無(wú)障礙焦點(diǎn)
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {// 分析1
            // 獲取Touch Action
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) { // 分析2
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            // 判斷事件是否需要攔截
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                // 判斷是否運(yùn)行不允許攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                // 如果允許攔截
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev); // 分析3
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    // 不攔截狀態(tài)下,intercepted = false
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {// 分析4

                // If the event is targeting accessibility focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                // 因?yàn)檫€是處于按下?tīng)顟B(tài)饲嗽,所以當(dāng)前為ACTION_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
                    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);
                    // 對(duì)于當(dāng)前事件炭玫,有處理權(quán)限的View的個(gè)數(shù)(肯定不為0)
                    final int childrenCount = mChildrenCount;
                    // 因?yàn)閚ewTouchTarget還未賦值,所以為null
                    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();// 分析7
                        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 there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            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.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        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;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {// 分析5
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {// 分析6
                // 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;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // 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) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

父容器不攔截子View事件貌虾,intercepted 為 false吞加,canceled也為false,在[分析4]處的代碼塊會(huì)被執(zhí)行

  • 分析7(當(dāng)前在ViewGroup類)
final ArrayList<View> preorderedList = buildTouchDispatchChildList();

public ArrayList<View> buildTouchDispatchChildList() {
    return buildOrderedChildList();
}
    ArrayList<View> buildOrderedChildList() {
        final int childrenCount = mChildrenCount;
        if (childrenCount <= 1 || !hasChildWithZ()) return null;

        if (mPreSortedChildren == null) {
            mPreSortedChildren = new ArrayList<>(childrenCount);
        } else {
            // callers should clear, so clear shouldn't be necessary, but for safety...
            mPreSortedChildren.clear();
            mPreSortedChildren.ensureCapacity(childrenCount);
        }

        final boolean customOrder = isChildrenDrawingOrderEnabled();
        for (int i = 0; i < childrenCount; i++) {
            // add next child (in child order) to end of list
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View nextChild = mChildren[childIndex];
            final float currentZ = nextChild.getZ();

            // insert ahead of any Views with greater Z
            int insertIndex = i;
            while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
                insertIndex--;
            }
            mPreSortedChildren.add(insertIndex, nextChild);
        }
        return mPreSortedChildren;
    }

對(duì)當(dāng)前有處理事件權(quán)限的View做一個(gè)排序,將View按照getZ()方法為排序依據(jù)一個(gè)個(gè)的放入ArrayList數(shù)組中
getZ方法拿到的是View的Z坐標(biāo)衔憨,getZ越大叶圃,在數(shù)組中的位置越靠后。

image.png

如果一個(gè)Activity中包含一個(gè)LinearLayout巫财,LinearLayout中有子View為Button盗似,那么數(shù)組的排序?yàn)锳ctivity、LinearLayout平项、Button赫舒。

在[分析7]處,依據(jù)拿到了排過(guò)序的ArrayList闽瓢,進(jìn)入[分析8]

  • 分析8(當(dāng)前在ViewGroup類)
// 這里做了倒序處理(如上圖接癌,就是先把Button取出來(lái)了)
for (int i = childrenCount - 1; i >= 0; i--) {
    // 獲取child對(duì)應(yīng)的index
    final int childIndex = getAndVerifyPreorderedIndex(
            childrenCount, i, customOrder);
    // 從數(shù)組中獲取View
    final View child = getAndVerifyPreorderedView(
             preorderedList, children, childIndex);
    ...

     if (!canViewReceivePointerEvents(child)
         || !isTransformedTouchPointInView(x, y, child, null)) { // 分析8-1
         ev.setTargetAccessibilityFocus(false);
         continue;
    }

     newTouchTarget = getTouchTarget(child); // 分析8-2
     ...
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// 分析8-3
        // Child wants to receive touch within its bounds.
        mLastTouchDownTime = ev.getDownTime();
        if (preorderedList != null) {
            // childIndex points into presorted list, find original index
            for (int j = 0; j < childrenCount; j++) {
                if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                }
             }
        } else {
            mLastTouchDownIndex = childIndex;
        }
        mLastTouchDownX = ev.getX();
        mLastTouchDownY = ev.getY();
        newTouchTarget = addTouchTarget(child, idBitsToAssign);
        alreadyDispatchedToNewTouchTarget = true;
        break;
    }
}
  • 分析8-1(當(dāng)前在ViewGroup類)
// 如果是View不可見(jiàn)或者正在執(zhí)行動(dòng)畫(huà),則返回false扣讼;否則返回true
private static boolean canViewReceivePointerEvents(@NonNull View child) {
    return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
}
// 判斷當(dāng)前位置
protected boolean isTransformedTouchPointInView(float x, float y, View child,
        PointF outLocalPoint) {
    final float[] point = getTempPoint();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}

[分析8-1]處的代碼缺猛,主要是分析當(dāng)前View是否符合處理事件的條件,如果不符合椭符,continue荔燎。

  • 分析8-2(當(dāng)前在ViewGroup類)
newTouchTarget = getTouchTarget(child);

private TouchTarget getTouchTarget(@NonNull View child) {
    for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
        if (target.child == child) {
            return target;
        }
    }
    return null;
}

由于mFirstTouchTarget目前暫未賦值,還是為null销钝,所以方法體返回null有咨,即newTouchTarget為null

  • 分析8-3(當(dāng)前在ViewGroup類)

這次流程進(jìn)入了dispatchTransformedTouchEvent方法中,和之前遇到的[分析5-1]代碼流程不同蒸健,這時(shí)的第三個(gè)入?yún)iew child是有值的座享,按照之前分析的流程來(lái)看,這里傳入的應(yīng)該是button似忧。

    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();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        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.
        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.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        // 此時(shí)child不為null
        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());
            }
            // 重點(diǎn)代碼渣叛,調(diào)用了child(也就是View)的dispatchTouchEvent方法。
            handled = child.dispatchTouchEvent(transformedEvent);
        }

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

調(diào)用 child.dispatchTouchEvent方法盯捌,流程就進(jìn)入到了View類中的 dispatchTouchEvent方法里了
dispatchTransformedTouchEvent方法會(huì)返回true,也就是[分析8-3]處的代碼返回為true淳衙,進(jìn)入if語(yǔ)句塊。

再次放出[分析8-3]處if語(yǔ)句塊中代碼

    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // Child wants to receive touch within its bounds.
        mLastTouchDownTime = ev.getDownTime();
        if (preorderedList != null) {
            // childIndex points into presorted list, find original index
            for (int j = 0; j < childrenCount; j++) {
                if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                }
             }
        } else {
            mLastTouchDownIndex = childIndex;
        }
        mLastTouchDownX = ev.getX();
        mLastTouchDownY = ev.getY();
        newTouchTarget = addTouchTarget(child, idBitsToAssign); // 分析9
        alreadyDispatchedToNewTouchTarget = true;
        // 跳出for循環(huán)
        break;
    }

我們知道饺著,如果是子View處理了事件滤祖,那么其父容器是無(wú)法處理該事件的,為什么呢瓶籽?我們繼續(xù)分析addTouchTarget方法

  • 分析9(當(dāng)前在ViewGroup類)
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    // 根據(jù)child獲取到TouchTarget
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    // 此時(shí) mFirstTouchTarget依然為null,所以target.next為null
    target.next = mFirstTouchTarget;
    // target賦值給mFirstTouchTarget埂材,此時(shí)mFirstTouchTarget不為null
    mFirstTouchTarget = target;
    return target;
}

public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
    ...
    final TouchTarget target;
    // 同步方法
    synchronized (sRecycleLock) {
        if (sRecycleBin == null) {
            // 首次進(jìn)來(lái) new 一個(gè)TouchTarget
            target = new TouchTarget();
        } else {
            target = sRecycleBin;
            sRecycleBin = target.next;
            sRecycledCount--;
            target.next = null;
        }
    }
    // 傳進(jìn)來(lái)的View賦值給target.child塑顺,并將target返回出去
    target.child = child;
    target.pointerIdBits = pointerIdBits;
    return target;
}

此時(shí)再看[分析8-3]處的代碼:
newTouchTarget不為null,
newTouchTarget.next為null,
mFirstTouchTarget不為null,
mFirstTouchTarget.next為null严拒,
且newTouchTarget = mFirstTouchTarget扬绪,
alreadyDispatchedToNewTouchTarget為true

根據(jù)剛才我們得到的結(jié)果,可以看到流程已經(jīng)開(kāi)始執(zhí)行下面代碼

TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
// 首次target不為null
while (target != null) {
    // 由于 target.next為null裤唠,此時(shí)next為null
    final TouchTarget next = target.next;
    // alreadyDispatchedToNewTouchTarget為true
    // target == newTouchTarget根據(jù)之前分析的為true
    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
        // handled為true
        handled = true;
    } else {
        final boolean cancelChild = resetCancelNextUpFlag(target.child)
              || intercepted;
        if (dispatchTransformedTouchEvent(ev, cancelChild,
              target.child, target.pointerIdBits)) {
             handled = true;
        }
        if (cancelChild) {
            if (predecessor == null) {
                mFirstTouchTarget = next;
            } else {
                predecessor.next = next;
            }
            target.recycle();
            target = next;
            continue;
        }
    }
    predecessor = target;
    // 將next賦值給target挤牛,而此時(shí)的next為null,所以target為null种蘸,跳出while循環(huán)
    target = next;
}}

流程到此墓赴,ViewGroup的dispatchTouchEvent方法返回為true, 則回到 [分析5]處, [分析5]的代碼也會(huì)返回true航瞭,則會(huì)回到Activity的dispatchEvent方法中诫硕。

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

getWindow().superDispatchTouchEvent(ev)返回為true,至此刊侯,在“父容器不攔截事件”的情況下的流程已經(jīng)走完章办。

  • ViewGroup對(duì)點(diǎn)擊事件的分發(fā)機(jī)制總結(jié)
image.png
  • View對(duì)點(diǎn)擊事件的分發(fā)機(jī)制
    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }
        // 檢測(cè)是否分發(fā)Touch事件(判斷窗口是否被遮擋住)
        // 如果該 Touch 事件沒(méi)有被窗口遮擋滨彻,則繼續(xù)后續(xù)邏輯
        // 一般情況下都會(huì)返回true
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo; // 分析10
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {// 分析11
                result = true;
            }

            if (!result && onTouchEvent(event)) {// 分析12
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }
  • 分析10(當(dāng)前在View類)
 ListenerInfo li = mListenerInfo; 

那么mListenerInfo是在哪里賦值的呢藕届?

  • 分析10(當(dāng)前在View類)
ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

繼續(xù)尋找getListenerInfo方法調(diào)用的地方

  • 分析10(當(dāng)前在View類)
public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

setOnClickListener方法是Android開(kāi)發(fā)者最最常用的一個(gè)方法了,執(zhí)行此方法亭饵,傳遞一個(gè)OnClickListener進(jìn)來(lái)休偶。

  • 分析11(當(dāng)前在View類)
if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
    result = true;
}
  • 分析12(當(dāng)前在View類)
if (!result && onTouchEvent(event)) {
    result = true;
}

[分析11]和[分析12]可以結(jié)合起來(lái)看,如果某個(gè)View重寫(xiě)了onTouch方法并返回為true冬骚,就不能執(zhí)行onTouchEvent方法椅贱。也就是說(shuō)執(zhí)行了[分析11]處的代碼,[分析12]就不會(huì)執(zhí)行了只冻。

當(dāng)然庇麦,如果不重寫(xiě)onTouch,或者onTouch方法返回false喜德,就會(huì)執(zhí)行[分析12]處的onTouchEvent山橄。我們經(jīng)常使用的onClick方法就是在此。

public boolean onTouchEvent(MotionEvent event) {
      ...
     if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
         switch (action) {
             // 如果是點(diǎn)擊事件舍悯,就得滿足先DOWN后UP
             // 那么就可以直接在ACTION_UP中查看方法
             case MotionEvent.ACTION_UP:
                 ...
                  performClickInternal();
                 ...
             case MotionEvent.ACTION_DOWN:
            ...
        }
     return true;
    }
 return false;
}

查看performClickInternal方法源碼

private boolean performClickInternal() {
    notifyAutofillManagerOnClick();
    return performClick();
}

繼續(xù)查看performClick方法源碼

public boolean performClick() {
    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;
}

可以看到航棱,這里執(zhí)行了OnClickListener接口的onClick回調(diào),也就是我們經(jīng)常使用的onClick方法了萌衬。

  • View對(duì)點(diǎn)擊事件的分發(fā)機(jī)制總結(jié)
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饮醇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子秕豫,更是在濱河造成了極大的恐慌朴艰,老刑警劉巖观蓄,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異祠墅,居然都是意外死亡侮穿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)毁嗦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)亲茅,“玉大人,你說(shuō)我怎么就攤上這事狗准】寺啵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵驶俊,是天一觀的道長(zhǎng)娶耍。 經(jīng)常有香客問(wèn)我,道長(zhǎng)饼酿,這世上最難降的妖魔是什么榕酒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮故俐,結(jié)果婚禮上想鹰,老公的妹妹穿的比我還像新娘。我一直安慰自己药版,他們只是感情好辑舷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著槽片,像睡著了一般何缓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上还栓,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天碌廓,我揣著相機(jī)與錄音,去河邊找鬼剩盒。 笑死谷婆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辽聊。 我是一名探鬼主播纪挎,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼跟匆!你這毒婦竟也來(lái)了异袄?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤玛臂,失蹤者是張志新(化名)和其女友劉穎隙轻,沒(méi)想到半個(gè)月后埠帕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玖绿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叁巨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斑匪。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖锋勺,靈堂內(nèi)的尸體忽然破棺而出蚀瘸,到底是詐尸還是另有隱情,我是刑警寧澤庶橱,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布贮勃,位于F島的核電站,受9級(jí)特大地震影響苏章,放射性物質(zhì)發(fā)生泄漏寂嘉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一枫绅、第九天 我趴在偏房一處隱蔽的房頂上張望泉孩。 院中可真熱鬧,春花似錦并淋、人聲如沸寓搬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)句喷。三九已至,卻和暖如春兔毙,著一層夾襖步出監(jiān)牢的瞬間唾琼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工瞒御, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留父叙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓肴裙,卻偏偏與公主長(zhǎng)得像趾唱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜻懦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容