1. 程式人生 > >事件分發機制的詳解及原始碼分析

事件分發機制的詳解及原始碼分析

事件分發機制詳解

MotionEvent 主要分為以下幾個事件型別:

ACTION_DOWN 手指開始觸控到螢幕的那一刻響應的是DOWN事件
ACTION_MOVE 接著手指在螢幕上移動響應的是MOVE事件
ACTION_UP 手指從螢幕上鬆開的那一刻響應的是UP事件
所以事件順序是: ACTION_DOWN -> ACTION_MOVE -> ACTION_UP

事件分發機制的三個主要方法:

public boolean dispatchTouchEvent(MotionEvent event) —— 分發事件

作用是用來進行事件的分發。一般在這個方法裡必須寫 return super.dispatchTouchEvent 。如果不寫super.dispatchTouchEvent,而直接改成
return true 或者 false,則事件傳遞到這裡時便終止了,既不會繼續分發也不會回傳給父元素。

public boolean onInterceptTouchEvent(MotionEvent event) —— 攔截事件

只有ViewGroup才有這個方法。View只有dispatchTouchEvent和onTouchEvent兩個方法。因為View沒有子View,所以不需要攔截事件。而ViewGroup
裡面可以包裹子View,所以通過onInterceptTouchEvent方法,ViewGroup可以實現攔截,攔截了的話,ViewGroup就不會把事件繼續分發給子View了
,也就是說在這個ViewGroup中的子View都不會響應到任何事件了。onInterceptTouchEvent 返回true時,表示ViewGroup會攔截事件。

public boolean onTouchEvent(MotionEvent event) —— 消費事件

onTouchEvent 返回true時,表示事件被消費掉了。一旦事件被消費掉了,其他父元素的onTouchEvent方法都不會被呼叫。如果沒有人消耗事件,則最終
當前Activity會消耗掉。則下次的MOVE、UP事件都不會再傳下去了。

需要注意的一些事項:

一般我們在自定義ViewGroup時不會攔截Down事件,因為一旦攔截了Down事件,那麼後續的Move和Up事件都不會再傳遞下去到子元素了,事件以後都會
只交給ViewGroup這裡。
一個Down事件分發完了之後,還有回傳的過程。因為一個事件分發包括了Action_Down、Action_Move、Action_Up這幾個動作。當手指觸控到螢幕的那一刻
,首先分發Action_Down事件,事件分發完後還要回傳回去,然後繼續從頭開始分發,執行下一個Aciton_Move操作,直到執行完Action_Up事件,整個事
件分發過程便到此結束。

事件分發機制的流程

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">

    <me.anany.ViewGroupA
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_blue_bright">

        <me.anany.ViewGroupB
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:background="@android:color/holo_green_dark">

            <me.anany.CustomView
                android:id="@+id/btn"
                android:text="Button"
                android:background="@android:color/holo_red_dark"
                android:layout_width="100dp"
                android:layout_height="100dp"
                />

        </me.anany.ViewGroupB>
    </me.anany.ViewGroupA>
</RelativeLayout>

圖片名稱

1.點選View區域但View不消耗事件
    流程圖解析:

事件分發
當在螢幕上點選一個View時,首先執行到的是MainActivity的dispatchTouchEvent方法,這裡便是事件分發的起點。紅色箭頭流向便是事件分發的流向。

事件傳遞到ViewGroupA時,因為它不攔截事件,所以它要先去問它的子控制元件ViewGroupB是否要消費事件,然後將事件分發給ViewGroupB。事件到了ViewGroupB
時,它不攔截事件,所以它也要先去問它的子控制元件們要不要消費事件,然後將事件分發給View。事件到了View時開始執行dispatchTouchEvent,因為已經到了最
底層了,View接下來便開始執行onTouchEvent方法來決定是否消費事件。

事件回傳
由於View沒有消費事件,所以它開始回傳資訊,(紫色箭頭的流向便是事件回傳方向),以告訴ViewGroupB我不消費事件了,view 的 onTouchEvent 便return false
。然後ViewGroupB才開始有權利決定我是否要開始消費事件(因為它已經問過它的子控制元件是否要消費事件了,而它的子控制元件並沒有消費),所以開始執行ViewGroupB的
onTouchEvent方法,由於ViewGroupB也不消費事件,所以它也 return false 。事件繼續回傳給ViewGroupA,這個時候它終於開始有權利決定我是否要消費事件了,
所以開始執行ViewGroupA的onTouchEvent方法,由於ViewGroupA也不感興趣不消費事件,所以它也return false。最終你們這些孩兒們都不消費事件,那事件最終只能
扔給MainActivity去消費了。

圖片名稱

2.點選View區域且View消耗事件
流程圖解析:

事件分發
當在螢幕上點選一個View時,首先執行到的是MainActivity的dispatchTouchEvent方法,這裡便是事件分發的起點。紅色箭頭流向便是事件分發的流向。

事件傳遞到ViewGroupA時,因為它不攔截事件,所以它要先去問它的子控制元件ViewGroupB是否要消費事件,然後將事件分發給ViewGroupB。事件到了ViewGroupB時,
它不攔截事件,所以它也要先去問它的子控制元件們要不要消費事件,然後將事件分發給View。事件到了View時開始執行dispatchTouchEvent,因為已經到了最底層了,
View接下來便開始執行onTouchEvent方法來決定是否消費事件。

事件回傳
由於View消費了事件,所以它開始回傳,(紫色箭頭的流向便是事件回傳方向),以告訴ViewGroupB我已經消費事件了,view 的 onTouchEvent 便return true。然後
ViewGroupB 收到了View return true 就知道事件已經被View消費掉了,所以不會執行ViewGroupB的onTouchEvent方法,只能往上回傳 return true 去告訴
ViewGroupA事件已經被消費掉了,你沒機會了 。然後事件繼續回傳給ViewGroupA,A收到return true 便知道 事件被消費了,所以它也return true。最終事件回傳到了
MainActivity,由於事件被消費了,所以不會執行MainActivity的onTouchEvent方法。接下來又開始執行Move事件了,流程又和之前的一樣重新開始處理。

圖片名稱

3 點選ViewGroupB區域但不消耗事件
很顯然在這裡點選的ViewGroupB區域,並不在View的範圍內,所以事件也不會分發到View。

圖片名稱

4 點選View區域,View消耗事件,但設定了View.onTouchListener
View的mOnTouchListener.onTouch方法優先於View的onTouchEvent方法被執行。

圖片名稱

事件分發機制原始碼分析

    原始碼妥妥的是最新版5.0: 我們先從Activity.dispatchTouchEveent()說起:

/**
 * Called to process touch screen events.  You can override this to
 * intercept all touch screen events before they are dispatched to the
 * window.  Be sure to call this implementation for touch screen events
 * that should be handled normally.
 *
 * @param ev The touch screen event.
 *
 * @return boolean Return true if this event was consumed.
 */
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
程式碼一看能感覺出來DOWN事件比較特殊。我們繼續走到onUserInteraction()程式碼中.

/**
 * 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.
 *
 * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
 * be accompanied by calls to {@link #onUserInteraction}.  This
 * ensures that your activity will be told of relevant user activity such
 * as pulling down the notification pane and touching an item there.
 *
 * <p>Note that this callback will be invoked for the touch down action
 * that begins a touch gesture, but may not be invoked for the touch-moved
 * and touch-up actions that follow.
 *
 * @see #onUserLeaveHint()
 */
public void onUserInteraction() {
}
但是該方法是空方法,沒有具體實現。 我們往下看getWindow().superDispatchTouchEvent(ev).
getWindow()獲取到當前Window物件,表示頂層視窗,管理介面的顯示和事件的響應;每個Activity 均會建立一個PhoneWindow物件, 是Activity和整個View系統互動的介面,但是該類是一個抽象類。 從文件中可以看到The only existing implementation of this abstract class is android.policy.PhoneWindow, which you should instantiate when needing a Window., 所以我們找到PhoneWindow類,檢視它的superDispatchTouchEvent()方法。

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
該方法又是呼叫了mDecor.superDispatchTouchEvent(event), mDecor是什麼呢? 從名字中我們大概也能猜出來是當前視窗最頂層的DecorView, Window介面的最頂層的View物件。

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
講到這裡不妨就提一下DecorView.

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    ...
}
它整合子FrameLayout所有很多時候我們在用佈局工具檢視的時候發現Activity的佈局FrameLayout的。就是這個原因。
好了,我們接著看DecorView中的superDispatchTouchEvent()方法。

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}
是呼叫了super.dispatchTouchEveent(),而DecorView的父類是FrameLayout所以我們找到FrameLayout.dispatchTouchEveent(). 我們看到FrameLayout中沒有重寫dispatchTouchEveent()方法,所以我們再找到FrameLayout的父類ViewGroup.看ViewGroup.dispatchTouchEveent()實現。 新大陸浮現了...

/**
 * {@inheritDoc}
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    // Consistency verifier for debugging purposes.是除錯使用的,我們不用管這裡了。
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }

    boolean handled = false;
    // onFilterTouchEventForSecurity()用安全機制來過濾觸控事件,true為不過濾分發下去,false則銷燬掉該事件。
    // 方法具體實現是去判斷是否被其它視窗遮擋住了,如果遮擋住就要過濾掉該事件。
    if (onFilterTouchEventForSecurity(ev)) {
        // 沒有被其它視窗遮住
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 下面這一塊註釋說的很清楚了,就是在`Down`的時候把所有的狀態都重置,作為一個新事件的開始。
        // 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();
            // 如果是`Down`,那麼`mFirstTouchTarget`到這裡肯定是`null`.因為是新一系列手勢的開始。
            // `mFirstTouchTarget`是處理第一個事件的目標。
        }

        // 檢查是否攔截該事件(如果`onInterceptTouchEvent()`返回true就攔截該事件)
        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 標記事件不允許被攔截, 預設是`false`, 該值可以通過`requestDisallowInterceptTouchEvent(true)`方法來設定,
            // 通知父`View`不要攔截該`View`上的事件。
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 判斷該`ViewGroup`是否要攔截該事件。`onInterceptTouchEvent()`方法預設返回`false`即不攔截。
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                // 子`View`通知父`View`不要攔截。這樣就不會走到上面`onInterceptTouchEvent()`方法中了,
                // 所以父`View`就不會攔截該事件。
                intercepted = false;
            }
        } else {
            // 註釋比較清楚了,就是沒有目標來處理該事件,而且也不是一個新的事件`Down`事件(新事件的開始), 
            // 我們應該攔截下他。
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }

        // Check for cancelation.檢查當前是否是`Cancel`事件或者是有`Cancel`標記。
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed. 這行程式碼為是否需要將當前的觸控事件分發給多個子`View`,
        // 預設為`true`,分發給多個`View`(比如幾個子`View`位置重疊)。預設是true
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

        // 儲存當前要分發給的目標
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;

        // 如果沒取消也不攔截,進入方法內部
        if (!canceled && !intercepted) {

            // 下面這部分程式碼的意思其實就是找到該事件位置下的`View`(可見或者是在動畫中的View), 並且與`pointID`關聯。
            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();
                    // 遍歷找子`View`進行分發了。
                    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);

                        // `canViewReceivePointerEvents()`方法會去判斷這個`View`是否可見或者在播放動畫,
                        // 只有這兩種情況下可以接受事件的分發

                        // `isTransformedTouchPointInView`判斷這個事件的座標值是否在該`View`內。
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }

                        // 找到該`View`對應的在`mFristTouchTarget`中的儲存的目標, 判斷這個`View`可能已經不是之前`mFristTouchTarget`中的`View`了。
                        // 如果找不到就返回null, 這種情況是用於多點觸控, 比如在同一個`View`上按下了多跟手指。
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // Child View已經接受了這個事件了
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            // 找到該View了,不用再迴圈找了
                            break;
                        }

                        resetCancelNextUpFlag(child);
                        // 如果上面沒有break,只有newTouchTarget為null,說明上面我們找到的Child View和之前的肯定不是同一個了, 
                        // 是新增的, 比如多點觸控的時候,一個手指按在了這個`View`上,另一個手指按在了另一個`View`上。
                        // 這時候我們就看child是否分發該事件。dispatchTransformedTouchEvent如果child為null,就直接該ViewGroup出來事件
                        // 如果child不為null,就呼叫child.dispatchTouchEvent
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 如果這個Child View能分發,那我們就要把之前儲存的值改變成現在的Child View。
                            // 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();
                            // 賦值成現在的Child View對應的值,並且會把`mFirstTouchTarget`也改成該值(mFristTouchTarget`與`newTouchTarget`是一樣的)。
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            // 分發給子`View`了,不用再繼續迴圈了
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                // `newTouchTarget == null`就是沒有找到新的可以分發該事件的子`View`,那我們只能用上一次的分發物件了。
                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;
                }
            }
        }

        // DOWN事件在上面會去找touch target
        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // dispatchTransformedTouchEvent方法中如果child為null,那麼就呼叫super.dispatchTouchEvent(transformedEvent);否則呼叫child.dispatchTouchEvent(transformedEvent)。
            // `super.dispatchTouchEvent()`也就是說,此時`Viewgroup`處理`touch`訊息跟普通`view`一致。普通`View`類內部會呼叫`onTouchEvent()`方法
            // 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;
                // 找到了新的子`View`,並且這個是新加的物件,上面已經處理過了。
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    // 否則都呼叫dispatchTransformedTouchEvent處理,傳遞給child
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;

                    // 正常分發
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }

                    // 如果是onInterceptTouchEvent返回true就會遍歷mFirstTouchTarget全部給銷燬,這就是為什麼onInterceptTouchEvent返回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;
}
接下來還要說說dispatchTransformedTouchEvent()方法,雖然上面也說了大體功能,但是看一下原始碼能說明另一個問題:

/**
 * Transforms a motion event into the coordinate space of a particular child view,
 * filters out irrelevant pointer ids, and overrides its action if necessary.
 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
 */
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();

    // 這就是為什麼時間被攔截之後,之前處理過該事件的`View`會收到`CANCEL`.
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            // 子`View`去處理,如果子`View`仍然是`ViewGroup`那還是同樣的處理,如果子`View`是普通`View`,普通`View`的`dispatchTouchEveent()`會呼叫`onTouchEvent()`.
            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的dispatchTouchEveent()有些地方會呼叫super.dispatchTouchEveent(),而ViewGroup的父類就是View,接下來我們看一下View.dispatchTouchEveent()方法:

/**
 * Pass the touch screen motion event down to the target view, or this
 * view if it is the target.
 *
 * @param event The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 */
public boolean dispatchTouchEvent(MotionEvent event) {
    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();
    }

    // 判斷該`View`是否被其它`View`遮蓋住。
    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            // 先執行`listener`.
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            // 執行`onTouchEvent()`.
            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;
}
通過上面的分析我們看到View.dispatchTouchEvent()裡面會呼叫到onTouchEvent()來消耗事件。那麼onTouchEvent()是如何處理的呢?下面我們看一下 View.onTouchEvent()原始碼:

/**
 * Implement this method to handle touch screen motion events.
 * <p>
 * If this method is used to detect click actions, it is recommended that
 * the actions be performed by implementing and calling
 * {@link #performClick()}. This will ensure consistent system behavior,
 * including:
 * <ul>
 * <li>obeying click sound preferences
 * <li>dispatching OnClickListener calls
 * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
 * accessibility features are enabled
 * </ul>
 *
 * @param event The motion event.
 * @return True if the event was handled, false otherwise.
 */
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;

    // 對disable按鈕的處理,註釋說的比較明白,一個disable但是clickable的view仍然會消耗事件,只是不響應而已。
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == 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));
    }

    // 關於TouchDelegate,文件中是這樣說的The delegate to handle touch events that are physically in this view
    // but should be handled by another view. 就是說如果兩個View, View2在View1中,View1比較大,如果我們想點選
    // View1的時候,讓View2去響應點選事件,這時候就需要使用TouchDelegate來設定。
    // 簡單的理解就是如果這個View有自己的時間委託處理人,就交給委託人處理。
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        // 這個View可點選
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                // 最好先看DOWN後再看MOVE最後看UP。
                // PFLAG_PREPRESSED 表示在一個可滾動的容器中,要稍後才能確定是按下還是滾動.
                // PFLAG_PRESSED 表示不是在一個可滾動的容器中,已經可以確定按下這一操作.
                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();
                    }

                    // 在前面`DOWN`事件的時候會延遲顯示`View`的`pressed`狀態,使用者可能在我們還沒有顯示按下狀態效果時就不按了.我們還是得在進行實際的點選操作時,讓使用者看到效果。
                    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) {
                        // 判斷不是長按

                        // 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();
                            }
                            // PerformClick就是個Runnable,裡面執行performClick()方法。performClick()方法中怎麼執行呢?我們在後面再說。
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    // 取消按下狀態,UnsetPressedState也是個Runnable,裡面執行setPressed(false)
                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }

                    removeTapCallback();
                }
                break;

            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;
                // performButtonActionOnTouchDown()處理滑鼠右鍵選單,有些View顯示右鍵選單就直接彈選單.一般裝置用不到滑鼠,所以返回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.
                // 就是遍歷下View層級,判斷這個View是不是在一個能scroll的View中。
                if (isInScrollingContainer) {
                    // 因為使用者可能是點選或者是滾動,所以我們不能立馬判斷,先給使用者設定一個要點選的事件。
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    // 傳送一個延時的操作,用於判斷使用者到底是點選還是滾動。其實就是在tapTimeout中如果使用者沒有滾動,那就是點選了。
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // 設定成點選狀態
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);
                    // 檢查是否是長按,就是過一段時間後如果還在按住,那就是長按了。長按的時間是ViewConfiguration.getLongPressTimeout()
                    // 也就是500毫秒
                    checkForLongClick(0);
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                // 取消按下狀態,移動點選訊息,移動長按訊息。
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                break;

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

                // Be lenient about moving outside of buttons, 檢查是否移動到View外面了。
                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;
}
上面講了Touch事件的分發和處理,隨便說一下點選事件:
我們平時使用的時候都知道給View設定點選事件是setOnClickListener()

/**
 * Register a callback to be invoked when this view is clicked. If this view is not
 * clickable, it becomes clickable.
 *
 * @param l The callback that will run
 *
 * @see #setClickable(boolean)
 */
public void setOnClickListener(OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    // `getListenerInfo()`就是判斷成員變數`mListenerInfo`是否是null,不是就返回,是的話就初始化一個。
    getListenerInfo().mOnClickListener = l;
}
那什麼地方會呼叫mListenerInfo.mOnClickListener呢?

/**
 * Call this view's OnClickListener, if it is defined.  Performs all normal
 * actions associated with clicking: reporting accessibility event, playing
 * a sound, etc.
 *
 * @return True there was an assigned OnClickListener that was called, false
 *         otherwise is returned.
 */
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;
}