View触摸事件分发

Posted by ooftf on July 15, 2021

触摸事件是从哪里来的

  1. InputEventReceiver.dispatchInputEvent
  2. ViewRootImpl$WindowInputEventReceiver.onInputEvent
  3. ViewRootImpl.enqueueInputEvent
  4. ViewRootImpl$ViewPostImeInputStage.processPointerEvent
  5. (DecorView)View.dispatchPointerEvent
  6. DecorView.dispatchTouchEvent
  7. WindowCallbackWrapper.dispatchTouchEvent
  8. Activity.dispatchTouchEvent
  9. PhoneWindow.superDispatchTouchEvent
  10. DecorView.superDispatchTouchEvent
  11. ViewGroup.dispatchTouchEvent

主要方法

1. dispatchTouchEvent

  • 是 touch 事件的入口也是出口,对于View来说整个 touch 事件都是在 dispatchTouchEvent 方法内执行的
  • 如果 ACTION_DOWN 事件返回 true 代表 View 接收后续事件,如果 ACTION_DOWN 返回 false 表示 不会接收后续事件
  • 只有 ACTION_DOWN 事件才会组建接收触摸事件的 children 链表 mFirstTouchTarget

2. onInterceptTouchEvent

  1. 任意事件返回 ture 后续事件不再调用 onInterceptTouchEvent
  2. 因为 onInterceptTouchEvent 返回 true 会导致 mFirstTouchTarget 被清空,而 onInterceptTouchEvent 只有在 mFirstTouchTarget 不为空的情况下才会被调用。 所以任意事件返回 ture 后续事件不再调用 onInterceptTouchEvent

3. setOnTouchEventListener -> onTouch

  • 如果返回 true 代表消费这个事件,那么就不会再执行 onTouchEvent 方法,如果返回 false 则会调用 onTouchEvent
  • onTouch 方法是在 View.dispatchTouchEvent 中被调用的,如果是 ViewGroup 那么是在 super.dispatchTouchEvent (因为 super = ViewGroup) 中被调用,
  • 只有当 childrenView 没有消费事件(即 mFirstTouchTarget 为空,或者 mFirstTouchTarget.dispatchTouchEvent 返回 false),才会调用 super.dispatchTouchEvent 进而间接调用 onTouch

4. onTouchEvent

  • onTouchEvent 调用方法 View.performClickInternal 间接调用 View.OnClickListener.onClick 方法

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
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        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;

            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //由于应用程序切换、ANR 或某些其他状态更改,系统层可能已删除前一个手势的 up 或 cancel 事件。
                //所以重置触摸相关的数据,设置为初始状态
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 处理拦截事件
            final boolean intercepted;
            // 判断触摸事件为 ACTION_DOWN 或者 mFirstTouchTarget 不为 Null 
            // 从这里可以看出 ACTION_DOWN 对于 onInterceptTouchEvent 也是一个特殊事件,但影响不是很大;
            // mFirstTouchTarget 是接受触摸事件的 child 组成的一个链表
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                 // 判断是否允许拦截,这里对应方法  requestDisallowInterceptTouchEvent()            
                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 {
                // 如果 mFirstTouchTarget 为空 intercepted 强制设为 true ,不会调用 onInterceptTouchEvent() 方法
                // 因为 ACTION_DOWN 是初始事件,mFirstTouchTarget 必定为 null ,需要特殊判断一下
                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);
            }

            // 判断是否是 ACTION_CANCEL 事件
            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) {

                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                // 如果这个事件是 ACTION_DOWN 事件
                // 这里体现了 ACTION_DOWN 在整个事件分发流程中的特殊性,因为只有 ACTION_DOWN 事件,才会执行下面的流程
                // 条件为 true :内部具体做了什么呢
                // 遍历 child 
                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.
                        // 获取按照 Z 轴排序后的 chidren 集合,
                        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);
                            // 从  preorderedList 获取对应位置的 children       
                            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;
                            }
                            // 如果 children 不接受触摸事件 或者 并且触摸点不在 children 内直接跳过
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            // 获取 child 对应的 TouchTarget 对象
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                //如果 TouchTarget 已经存在添加触摸 ID  ,主要针对多点触摸
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                // 跳出循环
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            // 将事件传递给 children
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                
                                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 生成 TouchTarget 对象,并添加到接受触摸事件的 mFirstTouchTarget 链表中
                                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);
                        }
                        // 清空 preorderedList 
                        if (preorderedList != null) preorderedList.clear();
                    }

                    // 如果没有找到新的 child 接收事件,将 mFirstTouchTarget 的最后一个节点赋值给 newTouchTarget 
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // 如果 children 接收事件的 mFirstTouchTarget 为 null,表示没有 child 接收事件。
            // 如果没有 children 接收事件,就将事件通过 dispatchTransformedTouchEvent 传递给 super.dispatchTouchEvent

            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
     
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                // 遍历 mFirstTouchTarget 列表
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        // 如果这个 target 是新添加的
                        // 因为在上面已经调用过 dispatchTransformedTouchEvent 所以不用传递事件,直接将 handled 为 true
                        handled = true;
                    } else {
                        // cancelChild 表示是取消事件或者需要被拦截
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;

                        // 将事件通过 dispatchTransformedTouchEvent 传递给 View

                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        // 取消事件或者需要被拦截:
                        // 将 traget 回收,
                        // 将 next 赋值给 mFirstTouchTarget,这会导致 mFirstTouchTarget 最终值为 null ,也就标志着 chilren 接收触摸事件的链表 mFirstTouchTarget 会被清空
                        // mFirstTouchTarget 清空也就不会再执行 onInterceptTouchEvent
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // 如果是取消或者是 ACTION_UP 事件,重置触摸事件相关数据
            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
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
//ViewGroup.dispatchTouchEvent伪代码
fun dispatchTouchEvent(ev:MotionEvent):Boolean{
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        mFirstTouchTarget = null
    }
    if(MotionEvent.ACTION_DOWN || mFirstTouchTarget != null){
         if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
         } else {
            intercepted = false;
         }
    }else{
        intercepted = false;
    }
    if(!intercepted){
        if(actionMasked == MotionEvent.ACTION_DOWN){
            if(child.dispatchTouchEvent(ev)){
                addTouchTarget(child)// mFirstTouchTarget赋值为TouchTarget的头节点
            }
        }
    }
    boolean handled = false;
    if(mFirstTouchTarget == null){ // onInterceptTouchEvent 拦截了代码 或者 childView.dispatchTouchEvent 返回false
        // 执行View的touch系列事件,具体代码就是View.dispatchTouchEvent
        if(!handled){
            handled = mOnTouchListener.onTouch(ev)
        }
        if(!handled){
            handled = onTouchEvent(ev)
        }
        
    }else{
        //有个避免ACTION_DOWN二次调用的逻辑没有写
         //执行TouchTarget链表内View的事件
        var handler
        while(mFirstTouchTarget.hasNext){
            if(child.dispatchTouchEvent(ev)){
                handler = true
            }
        }
        // 如果被拦截清空 mFirstTouchTarget
        if(intercepted){
            mFirstTouchTarget = null
        }
       
    } 
    return handler
}

总结

  • 如果 onInterceptTouchEvent 如果返回 true -> mFirstTouchTarget = null -> 后续 evnet 不会调用 onInterceptTouchEvent
  • ACTION_DOWN 事件 onInterceptTouchEvent 返回 false -> mFirstTouchTarget 被赋值 -> event 调用 onInterceptTouchEvent
  • 如果 mFirstTouchTarget 为 null 则会执行 View 的 touch 事件;如果 mFirstTouchTarget 不为 null 则会执行 TouchTarget 的子的touch事件
  • 生成 mFirstTouchTarget 链表的动作只有在 ACTION_DOWN ACTION_POINTER_DOWN ACTION_HOVER_MOVE 事件产生
  • 如果在 ACTION_DOWN 事件时子 View 的 dispatchTouchEvent 返回为 false 就会导致 该View没有加入到 mFirstTouchTarget 链表中,就会导致后续接收不到事件
  • mFirstTouchTarget 是否为 null 是由 ACTION_DOWN 时 onInterceptTouchEvent 和 ChildrenView.dispatchTouchEvent 的返回值决定的
  • mFirstTouchTarget 之所以是个链表,是因为 “多点触摸事件”

View.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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    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();

        // 如果是 ACTION_DOWN 事件,停止滚动
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            // 如果 enable = true 并且在处理滚动 bar 直接返回 true
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            // 如果 mOnTouchListener 不为 null 并且 返回 true,直接 return true
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            // 如果事件还没有被处理,调用 onTouchEvent 方法
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

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

        // 如果手指已经抬起或者取消,或者 ACTION_DOWN 事件没消耗,停止滚动
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

总结:判断事件是否被 mOnTouchListener 消费,如果没有被消费 调用 onTouchEvent 方法

实践问题

有两个 Button 在 LinearLayout 中,如果点击在 A Button 上 move 到 B Button 上,然后抬起,并且 A,B都设置了点击事件,结果会怎样

结论:两个 Button 都不会响应点击事件

B 为什么不会响应: 因为 点击事件是将 A 添加到 接收列表中,B 根本没有添加到列表中,所以 B 不会接收到任何触摸事件

A 为什么不会响应: A 虽然 接收了完成触摸事件流程(ACTION_DWON , ACTION_MOVE , ACTION_UP)但是 ACTION_UP 不在 A 的控件范围内,所以 A 不会触发 Listener

代码片段

判断手势滑动方向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    /**
     * 判断滑动的方向
     * @param dx
     * @param dy
     * @return
     */
    private Direction getOrientation(float dx, float dy) {
        if (Math.abs(dx)>Math.abs(dy)){
            //X轴移动
            return dx>0?Direction.RIGHT:Direction.LEFT;//右,左
        }else{
            //Y轴移动
            return dy>0?Direction.BOTTOM:Direction.TOP;//下//上
        }
    }