1. 程式人生 > >Android原始碼解析(三十)-->觸控事件分發流程

Android原始碼解析(三十)-->觸控事件分發流程

前面一篇文章中我們分析了App返回按鍵的分發流程,從Native層到ViewRootImpl層到DocorView層到Activity層,以及在Activity中的dispatchKeyEvent方法中分發事件,最終呼叫了Activity的finish方法,即銷燬Activity,所以一般情況下假如我們不重寫Activity的onBackPress方法或者是onKeyDown方法,當我們按下並擡起返回按鍵的時候預設都是銷燬當前Activity。而本文中我們主要介紹觸控事件的分發流程,從Native層到Activity層觸控事件的分發了流程和按鍵的分發事件都是類似的,這裡我們可以根據異常堆疊資訊看一下。

at com.example.aaron.helloworld.MainActivity.dispatchTouchEvent(MainActivity.java:103)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2359)
at android.view.View.dispatchPointerEvent(View.java:8698)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent
(ViewRootImpl.java:4530) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4388) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3924) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3977) at android.view.ViewRootImpl$InputStage.forward
(ViewRootImpl.java:3943) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4053) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3951) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4110) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3924) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3977) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3943) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3951) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3924) at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6345) at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6301) at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6254) at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6507) at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)

這樣經過一系列的方法呼叫之後最終呼叫了Activity的dispatchTouchEvent方法,而我們也是從Activiyt的dispatchTouchEvent方法開始對觸控事件的分發進行分析。

在具體檢視Activity的dispatchTouchEvent方法之前我們先簡單介紹一下觸控事件,觸控事件是由一個觸控按下事件、N個觸控滑動事件和一個觸控擡起事件組成的,通常的一個觸控事件中只能存在一個觸控按下和一個觸控擡起事件,但是觸控滑動事件可以有零個或者多個。好了,知道這個概念以後,下面我們就具體看一下Activity中的dispatchTouchEvent的實現邏輯。

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

在看一下dispatchTouchEvent方法之前我們首先需要解釋一下MotionEvent的概念。MotionEvent是一個觸控動作的封裝,裡面包含了觸控動作的型別,以及操作等屬性,我們具體的可以看一下MotionEvent的說明:

Object used to report movement (mouse, pen, finger, trackball) events. Motion events may hold either absolute or relative movements and other data, depending on the type of device.

然後在dispatchTouchEvent方法中,會首先判斷MotionEvent的動作型別,也就是我們的觸目動作的型別,判斷其是否是“按下”操作,若是的湖澤,則執行onUserInteraction方法,這個方法又是實現了什麼邏輯呢?

public void onUserInteraction() {
    }

可以發現其在Activity中只是一個簡單的空實現方法,同樣的我們可以看一下該方法的介紹:

Called whenever a key, touch, or trackball event is dispatched to the activity. Implement this method if you wish to know that the user has interacted with the device in some way while your activity is running. This callback and {@link #onUserLeaveHint} are intended to help activities manage status bar notifications intelligently; specifically, for helping activities determine the proper time to cancel a notfication.

理解上就是使用者在觸屏點選,按home,back,menu鍵都會觸發此方法。

回到Activity的dispatchTouchEvent方法中,我們呼叫了getWindow().suerDispatchTouchEvent()方法,我們分析過Activity的載入繪製流程,而這裡的getWindow()就是返回Activity中的mWindow物件,而我們知道Activity中的mWindow物件就是一個PhoneWindow的例項。並且這裡的window.superDispatchTouchEvent若返回值為ture,則直接返回true,否則的話會執行Activity的onTouchEvent方法,繼續我們看一下PhoneWindow的superDispatchTouchEvent方法。

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

可以看到在PhoneWindow中的superDispatchTouchEvent方法中呼叫的是mDecor.superDispatchTouchEvent方法,而這裡的mDecor是我們Activity顯示的ViewTree的根View,並且mDecor是一個FrameLayout的子類,所以這裡我們看一下mDecor的superDispatchTouchEvent方法。

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    ...
    public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }
    ...
}

在DecorView的superDispatchTouchEvent方法中我們呼叫了super.dispatchTouchEvent方法,而我們的DecorView繼承於FrameLayout,但是經過檢視之後我們知道FrameLayout中並沒有實現dispatchTouchEvent方法,而由於我們的FrameLayout繼承於ViewGroup,所以這裡的dispatchTouchEvent方法應該就是ViewGroup的dispatchTouchEvent方法。

好了,這裡先暫時說一下Acitivty中的事件分發流程

  • ViewRootImpl層的事件分發會首先呼叫Activity的dispatchTouchEvent方法;

  • Activity的dispatchTouchEvent方法中會通過Window.superDispatchTouchEvent方法將事件傳遞給DecorView即ViewGroup。

  • 若window的superDispatchTouchEvent方法返回true,則事件分發完成,Activity的dispatchTouchEvent直接返回為true,否則的話呼叫Activity的onTouchEvent方法,並且Acitivty的dispatchTouchEvent返回值與Activity的onTouchEvent返回值一致。

下面我們在繼續看一下ViewGroup的dispatchTouchEvent方法。

public boolean dispatchTouchEvent(MotionEvent ev) {
        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.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

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

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // 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) {
                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 {
                // 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) {

                // If the event is targeting accessiiblity 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 = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(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) {
                // No touch targets so treat this as an ordinary view.
                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;
                        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;
    }

前面我們知道觸控事件是由一個觸控按下事件,一個觸控擡起事件和N個觸控滑動事件組成的,而這裡的觸控按下事件就是這裡的ACTION_DOWN,同時友誼ACTION_DOWN是一系列事件的開端,所以我們在ACTION_DOWN時進行一些初始化操作,從上面原始碼中註釋也可以看出來,清除以往的Touch狀態然後開始新的手勢。並在在cancelAndClearTouchTargets(ev)方法中將mFirstTouchTarget設定為了null,接著在resetTouchState()方法中重置Touch狀態標識。

然後標記ViewGroup是否攔截Touch事件的傳遞,if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)這一條判斷語句說明當事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經找到能夠接收touch事件的目標元件)時if成立,否則if不成立,然後將intercepted設定為true,也即攔截事件。這裡說明一下ViewGroup中的onInterceptTouchEvent方法是ViewGroup中特有的方法用於表示是否攔截觸控事件,返回為true的話則表示攔截事件,事件不在向子View中分發,若範圍為false的話,則表示不攔截事件,繼續分發事件。

public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

一般的我們可以在自定義的ViewGroup中重寫該方法,用於攔截事件的分發。而當我們在父ViewGroup重寫該方法返回為true執行事件攔截的邏輯的時候,可以在子View中通過呼叫requestDisallowInterceptTouchEvent方法,重新設定父ViewGroup的onInterceptTouchEvent方法為false,不攔截對事件的分發邏輯。

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

比如常見的向我們的ViewPager中由於需要處理左右滑動事件從而在其onInterceptTouchEvent方法中重寫了返回值,返回為true,攔截對事件的處理邏輯,但是若這時候ViewPager中嵌套了ListView,則listView也需要處理觸控事件的邏輯,但是ViewPager中已經重寫了onInterceptTouchEvent方法,這時候怎麼辦呢?幸運的是ListView也在內部的實現中呼叫了requestDisallowInterceptTouchEvent方法,保證自身獲得對觸控事件的處理。

然後在程式碼中我們判斷childrenCount個數是否不為0,繼續我們獲取子View的list集合preorderedList;最後通過一個for迴圈倒序遍歷所有的子view,這是因為preorderedList中的順序是按照addView或者XML佈局檔案中的順序來的,後addView新增的子View,會因為Android的UI後重新整理機制顯示在上層;假如點選的地方有兩個子View都包含的點選的座標,那麼後被新增到佈局中的那個子view會先響應事件;也就是說後被新增的子view會浮在上層,點選的時候最上層的那個元件先去響應事件。

然後程式碼通過呼叫getTouchTarget去查詢當前子View是否在mFirstTouchTarget.next這條target鏈中的某一個targe中,如果在則返回這個target,否則返回null。在這段程式碼的if判斷通過說明找到了接收Touch事件的子View,即newTouchTarget,那麼,既然已經找到了,所以執行break跳出for迴圈。如果沒有break則繼續向下執行,這裡你可以看見一段if判斷的程式碼if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)),那麼這個方法又是執行什麼邏輯的呢?

在該方法中為一個遞迴呼叫,會遞迴呼叫dispatchTouchEvent()方法。在dispatchTouchEvent()中如果子View為ViewGroup並且Touch沒有被攔截那麼遞迴呼叫dispatchTouchEvent(),如果子View為View那麼就會呼叫其onTouchEvent()。dispatchTransformedTouchEvent方法如果返回true則表示子View消費掉該事件。

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.
        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;
    }

然後在在ViewGroup的dispatchTransformedTouchEvent方法中,呼叫了該ViewGroup的child View的dispatchTouchEvent方法,若其子View也是ViewGroup,則重複執行ViewGroup的dispatchTouchEvent方法,若其子View是View,則執行View的dispatchTouchEvent方法。

但這裡大概分析了一下ViewGroup的事件分發流程
- 首先在android的事件分發流程中,通過呼叫Activity的dispatchTouchEvent,事件會首先被派發是先傳遞到最頂級的DecorView也就是ViewGroup,再由ViewGroup遞迴傳遞到View的。

  • 在ViewGroup中可以通過設定onInterceptTouchEvent方法對事件傳遞進行攔截,onInterceptTouchEvent方法返回true代表不允許事件繼續向子View傳遞,返回false代表不對事件進行攔截,預設返回false。

下面我們繼續看一下View的dispatchTouchEvent方法。

public boolean dispatchTouchEvent(MotionEvent event) {
        ...
        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            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;
    }

View的dispatchTouchEvent方法的內容比較長,我們重點看一下View對觸控事件的處理邏輯,首先呼叫了onFilterTouchEventForSecurity(event)方法判斷當前的View是否被遮蓋,若沒有的話,則判斷View的mListenerInfo城邊變數是否為空,而這裡的mListenerInfo又是什麼呢?通過分析原始碼我們知道這裡的mListenerInfo是通過setOnClickListener方法設定的。

public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

可以當前View一旦執行了setOnClickListener方法改View的mListenerInfo就不為空,若後有判斷了該View是否可點選,最後是判斷View的onTouchListener的onTouch方法的返回值。

所以當我們為當前View設定了OnTouchListener並且返回值為true的話,則直接執行其onTouch方法,若onTouch方法返回為true的話,則直接返回不在執行後續的View的onTouchEvent方法,否則繼續執行View的onTouchEvent方法,而我們繼續看一下View的onTouchEvent方法的實現邏輯。

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
    }

在ACTION為MotionEvent.ACTION_UP時,我們經過層層呼叫最終執行了performClick,方法而這個方法中我們回調了View的OnClickListener的onClick方法。。。

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

所以View元件分發觸控事件的時候:

  • View控制元件會首先執行dispatchTouchEvent方法。

  • View控制元件在dispatchTouchEvent方法中先執行onTouch方法,後執行onClick方法。

  • View的onTouch返回false或者mOnTouchListener為null(控制元件沒有設定setOnTouchListener方法)或者控制元件不是enable的情況下會調運onTouchEvent,dispatchTouchEvent返回值與onTouchEvent返回一樣。

  • View控制元件不是enable的,那麼即使設定了onTouch方法也不會執行,只能通過重寫控制元件的onTouchEvent方法處理,dispatchTouchEvent返回值與onTouchEvent返回一樣。

  • 如果控制元件(View)是enable且onTouch返回true情況下,dispatchTouchEvent直接返回true,不會呼叫onTouchEvent方法。