前言

android 事件体系比较繁杂,为了充分了解整个结构,我们需要完全搞清楚视图组成,事件来源,最后才是事件分发。

View Activity与WindowManager

完整的视图层级如下,Activity最终会内嵌一个PhoneWindow,在PhoneWindow会内嵌一个DecorView,在DecorView 中的content才是我们操作setContentView设置的布局。

View 层级

在视图的添加操作需要与WindowManagerService进行沟通,这样才能时刻与系统事件交互。而WindowManger 就是沟通中介,如下图可知,WindowManagerImpl是WindowManger的实现者,而WindowManagerGlobal确是真正的工作者。

WindowManger

在创建了ActivityThread之后,会开始创建Activity,具体过程如下。

View 添加时序

在时序图中,步骤1到步骤3中WindowManager将添加DecorView的命令转发,最后会创建一个ViewRootImpl的关键对象; 步骤5到步骤7ViewRootImpl会向WMS发出申请,希望向硬件获得一块绘制区域,具体为Surface;步骤8到步骤10就会在这块 区域上开始布局绘制,ViewRootImpl会启动DecorView布局的子View的Measure,Layout,Draw,来生成布局层级关系; 步骤12之后就会将生成的布局提交给WMS,这样才会在用户手机屏幕上显示出来布局效果。

View 添加框图

事件来源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//View
at java.lang.Thread.dumpStack(Thread.java:1348)
at com.zxy.touchdemo.ToucheText.onTouchEvent(ToucheText.java:26)
at android.view.View.dispatchTouchEvent(View.java:12527)
//ViewGroup
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3032)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3032)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3032)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3032)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3032)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3032)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
//DecorView
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:444)
//PhoneWindow
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830)
//Activity
at android.app.Activity.dispatchTouchEvent(Activity.java:3465)
//CallBack
at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
//DecorView
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:402)
at android.view.View.dispatchPointerEvent(View.java:12768)
//ViewRootImpl
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5270)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5070)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4585)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4638)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4604)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4744)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4612)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4801)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4585)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4638)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4604)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4612)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4585)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7313)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7282)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7243)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7419)
//InputEventReceiver
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:247)
//Native
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:326)
at android.os.Looper.loop(Looper.java:165)
at android.app.ActivityThread.main(ActivityThread.java:6806)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

上边是一次事件分发的函数调用栈轨迹,很明显,事件是从Native传给Java层,然后由完成分发

1
2
3
4
5
6
7
8
public abstract class InputEventReceiver {
    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event, displayId);
    }
}

在InputEventReceiver中dispatchInputEvent的注释明显,Native会调用这个方法,然后由这个方法完成分发。 然后会经过ViewRootImpl这个类,在ViewRootImpl中存在不同类型的InputStage,这些InputStage形成责任链 事件通过责任链,被能处理的InputStage环节所处理。

InputStage 关系图

InputStage 责任链

从日志信息得知,在责任链中最终被ViewPostImeInputStage给通过processPointerEvent方法消耗掉该事件。

事件来源

事件不会直接传给Activity,会先经过DecorView发送给Window Callback。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class DecorView  {
 private PhoneWindow mWindow;
 ...
 public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
 }
 ...
}

PhoneWindow的CallBack在创建Activity时由Activity传入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Activity implements Window.Callback,  {
    ...
    final void attach(...) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        ...
    }
    ...
}

事件传递与特征

经过上述流程之后,正式进入事件分发阶段。其实对于事件分发有很多的结论分析,本文只想对于源码有一个深入理解, 来反证这些结论。

Activity 对于事件的处理

事件首先会经过Activity。onUserInteraction这个方法在源码注释中表示用于通知当前Activity正在接收事件。 可以重写然后实现一些措施。随后会将事件进一步分发给PhoneWindow。同时,如果分发之后没有被处理 Activity会自己调用onTouchEvent进行处理。

1
2
3
4
5
6
7
8
9
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

PhoneWindow 会继续将事件向内部的DecorView分发。

1
2
3
4
5
6
7
public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ...
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
    ...
}

ViewGroup 对于事件的处理

dispatchTouchEvent

DecovrView实际为一个ViewGroup,所以我们只需要分析ViewGroup的分发逻辑即可。

对于ViewGroup需要重点分析的是dispatchTouchEvent方法,这里我们先按照结构呈现部分代码。 来大致了解运行流程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    //位置1
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }
    //位置2
    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 {
        intercepted = true;
    }
    ...
    //位置3
    if (!canceled && !intercepted) {
        ...
    }
    //位置4      
    if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
    } else {
        ...
    }
    //位置5
    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);
    }
    ...
}  

位置1表示每个新事件序列的开头事件DOWN来临时,会将状态全部初始化。

位置2需要了解一个点,就是mFirstTouchTarget这个变量不为空时,表示事件序列已经有合适的子View处理了,也就表示当前组件不曾拦截过这个 事件序列。几个条件混合之后表示为 ViewGroup有拦截一个新事件的资格,如果拦截成功,就不会再继续调用onInterceptTouchEvent,如果没有拦截成功,只要FLAG_DISALLOW_INTERCEPT不为1,就可以一直拥有拦截事件序列中剩余事件的资格

位置3是表示寻找符合资格的子View来作为处理事件序列的对象。

位置4表示没有子View能处理这个事时,当前ViewGroup会继续处理。

位置5表示在事件序列结束之后也会清空状态。

下面我们再继续看看位置3的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()? findChildWithAccessibilityFocus() : null;
//位置6
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;

    removePointersFromTouchTargets(idBitsToAssign);

    final int childrenCount = mChildrenCount;
    if (newTouchTarget == null && childrenCount != 0) {
        final float x = ev.getX(actionIndex);
        final float y = ev.getY(actionIndex);
        //位置7
        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
        final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();
        final View[] children = mChildren;
        for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            if (childWithAccessibilityFocus != null) {
                if (childWithAccessibilityFocus != child) {
                    continue;
                }
                childWithAccessibilityFocus = null;
                i = childrenCount - 1;
            }

            if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {
                ev.setTargetAccessibilityFocus(false);
                continue;
            }
            //位置8
            newTouchTarget = getTouchTarget(child);
            if (newTouchTarget != null) {
                newTouchTarget.pointerIdBits |= idBitsToAssign;
                break;
            }

            resetCancelNextUpFlag(child);
            //位置9
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                mLastTouchDownTime = ev.getDownTime();
                if (preorderedList != null) {
                    for (int j = 0; j < childrenCount; j++) {
                        if (children[childIndex] == mChildren[j]) {
                            mLastTouchDownIndex = j;
                            break;
                        }
                    }
                } else {
                    mLastTouchDownIndex = childIndex;
                }
                mLastTouchDownX = ev.getX();
                mLastTouchDownY = ev.getY();
                //位置10
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }
            ev.setTargetAccessibilityFocus(false);
            }
            if (preorderedList != null) preorderedList.clear();
            }
            //位置11
            if (newTouchTarget == null && mFirstTouchTarget != null) {
                newTouchTarget = mFirstTouchTarget;
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                }
                newTouchTarget.pointerIdBits |= idBitsToAssign;
            }
        }
    }
}

位置6中表示下列这三种事件,会重新为事件寻找新的处理者。

事件 场景
ACTION_DOWN 单点触控
ACTION_POINTER_DOWN 多点触控
ACTION_HOVER_MOVE 鼠标移动

ACTION_DOWN表示触摸事件的开头,ACTION_POINTER_DOWN表示存在多点触控的时候,ACTION_HOVER_MOVE表示鼠标移动事件

位置7会创建一个根据Z坐标从小到大排列的子View列表,并且在接下来遍历循环时是从大到小的去寻找合适的子View来处理事件。也就表示在视图布局上是从上到下的去寻找

位置8表示会优先从已经处理过事件的View中去找到合适的,这块适用于ACTION_POINTER_DOWN和ACTION_HOVER_MOVE事件,ACTION_DOWN事件是最开始mFistTouchTarget为空时才会触发。

位置9表示开始分发事件给符合条件的子View去处理。

位置10表示子View能处理掉时,就将该子View添加给mFistTouchTarget链表。

位置11表示如果没有找到合适处理对象就将当前事件赋予最最先的事件处理者,在多点触控中,最先处理者就是ACTION_DOWN事件的处理者。

位置4之后代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 if (mFirstTouchTarget == null) {
    handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
    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;
    }
}

上述代码表示如果没有处理对象能处理事件,事件就交由当前ViewGroup处理,如果在存在多个处理对象时,比如多点触控的时候,会对事件进行剥离分发,这块在之后代码会深入分析。

dispatchTransformedTouchEvent

ViewGroup对于具体处理事件的代码主要集中在dispatchTransformedTouchEvent, 核心的处理逻辑如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
    ...
    //如果child为空,就表示需要当前ViewGroup来处理该事件
    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
    //如果不为空,表示需要child来处理该事件
        handled = child.dispatchTouchEvent(event);
    }
    ...
}

具体的细节分析如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
        final boolean handled;
        final int oldAction = event.getAction();
        //位置1
        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;
        }

        //位置2
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        if (newPointerIdBits == 0) {
            return false;
        }
        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 {
            //位置3
            transformedEvent = event.split(newPointerIdBits);
        }

        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            //位置4
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            //位置5
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        transformedEvent.recycle();
        return handled;
    }

位置1主要是对CANCEL事件进行分发处理。

位置2会对事件进行一个剥离,在多点触控的情况下是会存在一个事件有包含多个受众View,需要将相应的受众View在TouchTarget中记录的事件 剥离出来,然后在进行一个分发。

位置3MotionEvent中getX和getY表示的当前这个事件距离这个当前受众View左上角的距离,所以当传给子View去处理时需要调用offsetLocation去 重新调整MotionEvent的坐标。

位置4中hasIdentityMatrix是用来判断子View是否经过其他转换(位移,旋转等),如果经过转换,还需要将MotionEvent的做相应的 转换,然后才能传给子View去处理

位置5表示需要将事件进行剥离,比如在多点触控中,一个事件其实包含了几个触控点,需要找到在一开始为POINT_DOWN对应的触控点,才能将这个事件准确发送给 合适的子View去处理

结论

一次事件序列是以一个DOWN开始一个UP结尾,中间会穿插多个MOVE,也会在多点触控这样的场景中穿插POINT_DOWN和POINT_UP事件。在ViewGroup中每次DOWN和POINT_DOWN到来时都会找寻一个合适的子View处理者加入到mFirstTouchTarget链表中,之后剩余事件来临时,会在mFirstTouchTarget中去找合适的处理者。如果mFirstTouchTarget为空,就表示从一开始没有找到合适的处理者,也不会继续从子View们中去寻找了,直接交给ViewGroup处理了。

View 对于事件的处理

dispatchTouchEvent

不管是ViewGroup还是View在真正去处理事件的时候都是调用的dispatchTouchEvent去处理的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    if (onFilterTouchEventForSecurity(event)) {
        //位置1
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //位置2
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //位置3
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ...
}

位置1表示如果当前View处于激活状态,并且是属于鼠标拖拽滑动按钮时(当前view存在滑动按钮时)会处理的事件,这时就会在条件满足的情况下完成View滑动

位置2表示如果当前View处于激活状态,如果存在TouchListener,会优先调用TouchListener来处理事件

位置3如果TouchListener不存在或者这个事件没有被处理掉,才会调用onTouchEvent继续处理

onTouchEvent

对于View 的onTouchEvent我们分成极端来分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        //位置1
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            return clickable;
        }
        //位置2
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        ...
 }

位置1中表示如果View不可用时,事件处理结果完全取决于View能否点击,但是这样实际是没有意义的,因为没有经过任何点击和长按事件

位置2表示如果当前View的区域是属于另外View的扩展点击区域,就直接将这个事件分发给另外的View,TouchDelegate本身就是用来做扩大一个View点击区域之用

然后开始分析对于事件的分类处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
...
 case MotionEvent.ACTION_DOWN:
    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
    }
    mHasPerformedLongPress = false;

    if (!clickable) {
        checkForLongClick(0, x, y);
        break;
    }
    //位置1
    if (performButtonActionOnTouchDown(event)) {
        break;
    }
    boolean isInScrollingContainer = isInScrollingContainer();
    //位置2
    if (isInScrollingContainer) {
        mPrivateFlags |= PFLAG_PREPRESSED;
        if (mPendingCheckForTap == null) {
            mPendingCheckForTap = new CheckForTap();
        }
        mPendingCheckForTap.x = event.getX();
        mPendingCheckForTap.y = event.getY();
        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
    } else {
        setPressed(true, x, y);
        checkForLongClick(0, x, y);
    }
    break;
...

对于DONW事件主要是一个长按事件的处理,在符合条件的情况,会启动一个处理长按的延时Runnable,如果到了事件这个Runnable没有没移除,就会执行

位置1用于处理鼠标右键点击,这种场景会触发view 的PFLAG_CANCEL_NEXT_UP_EVENT标志,在下一个事件来临时会在ViewGroup中的dispatchTouchEvent 中将在mFirstTouchTarget中移除该view,表示这个view 不会接收余下事件。

位置2中表示一个对于滑动View的一个细致判断,这种情况下不会立马响应事件处理,会延时处理,同时会把PFLAG_PREPRESSED置位,PFLAG_PREPRESSED在UP事件中 会起到作用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
...
case MotionEvent.ACTION_MOVE:
    if (clickable) {
        drawableHotspotChanged(x, y);
    }
    //位置1
    if (!pointInView(x, y, mTouchSlop)) {
        removeTapCallback();
        removeLongPressCallback();
        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    }
    break;
...

然后是关于MOVE事件的处理,MOVE事件的处理比较简单。

位置1表示如果在按下的过程中,事件区域远离了View区域,长按响应会被取消。同时PFLAG_PRESSED和PFLAG_PREPRESSED都会被归位。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
case MotionEvent.ACTION_UP:
    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    if ((viewFlags & TOOLTIP) == TOOLTIP) {
        handleTooltipUp();
    }
    //位置1
    if (!clickable) {
        removeTapCallback();
        removeLongPressCallback();
        mInContextButtonPress = false;
        mHasPerformedLongPress = false;
        mIgnoreNextUpEvent = false;
        break;
    }
    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
    //位置2
    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
        boolean focusTaken = false;
        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
            focusTaken = requestFocus();
        }

        if (prepressed) {
            setPressed(true, x, y);
        }
        //位置3
        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
            removeLongPressCallback();
            if (!focusTaken) {
                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)) {
            mUnsetPressedState.run();
        }

        removeTapCallback();
    }
    mIgnoreNextUpEvent = false;
    break;

位置1表示如果View是不可点击状态,会立马取消掉长按Runnable,清空状态。

位置2如果PFLAG_PRESSED和PFLAG_PREPRESSED都没有置位,那么就不继续执行。两种都不满足的情况在上述MOVE事件代码分析中会被触发。

位置3表示如果长按没有响应的情况下才会响应普通点击事件。同时FOUSE_TONUCH_INMODE状态存在时,也不会响应点击事件。这种情况主要发生在你需要 将键盘收起只会才能触发点击事件的时候。

需要注意的是onTouchEvent是不会接收多点触控事件,实际在传入给View的dispatchTouchEvent之前,就已经将多点触控事件拆分给了普通的DOWN和UP事件

1
2
3
4
5
6
7
private boolean dispatchTransformedTouchEvent(...){
    ...
    //拆分
    transformedEvent = event.split(newPointerIdBits);
    ...

}

MontionEvent的split就将指定pointid的多点触控事件单独拆分出来。

结论

一个事件处理的优先级如下

1
onTouch(TouchListener) >  onTouchEvent > mTouchDelegate > (onClickListenter|onLongClickListener)

如果我们不小心点击一个按钮,滑动到按钮区域之外,就能避免事件的触发。

总结

网上流行的一些事件分发流程简图其实也不是很准确的,就比如事件传递中常用的冒泡式消费模型,如果子View的onTouchEvent不消耗一个事件,就会把事件传给父View的 onTouchEvent消耗,这个其实就是有情况区分的,一般来说如果一个View不消耗除了Down事件之外的事件,这些事件还是会继续分发给该View,最后这些事件都会回传给Activity,而不是传给父View的onTouchEvent,有时候,别人的结论我们不能盲目的追随,一定要 reading the fuck code,才能得到属于自己的答案。