【转】Android笔记:触摸事件的分析与总结----Touch事件分发方法dispatchTouchEvent()源码分析-阿里云开发者社区

开发者社区> 余二五> 正文

【转】Android笔记:触摸事件的分析与总结----Touch事件分发方法dispatchTouchEvent()源码分析

简介:
+关注继续查看

触摸事件学习系列文章详见:

《Android Touch事件学习系列汇总》


当前文章的源码基于Android 4.0 (Android 14)


一、Android如何分发事件

    从上一篇文章《Android Touch事件学习 7 交给哪个视图处理事件?》可以简单通过LOG角度了解下调用次序,本片对其具体分发的源码进行解读。

wKioL1QjhA_R90OxAACqAyjiRiU792.jpg


wKiom1QjhACgFDVdAAC0MQWExkE946.jpg


查看点击绿色视图的LOG

1
2
3
4
5
6
7
8
9
RelativeLayout dispatchTouchEvent ACTION_DOWN
LinearLayout dispatchTouchEvent ACTION_DOWN
CustomView dispatchTouchEvent ACTION_DOWN
CustomView onTouchEvent ACTION_DOWN
 
RelativeLayout dispatchTouchEvent ACTION_UP
LinearLayout dispatchTouchEvent ACTION_UP
CustomView dispatchTouchEvent ACTION_UP
CustomView onTouchEvent ACTION_UP



二、ViewGroup的子类(各种布局)如何分发事件?

    ViewGroup的子类都是布局,例如:RelativeLaout、LiearLayout、FrameLayout、GridLayout、AbsoluteLayout等都是其子类。


    结合上面的图片可以看到dispatchTouchEvent是针对UI树形结构由上向下传递,执行每一个View的dispatchTouchEvent方法,其虽然是View视图的方法,但是ViewGroup对其进行了覆写,ViewGroup.dispatchTouchEvent复杂一点,按照上面的图片和LOG来看是先执行RelativeLayout.dispatchTouchEvent,之后执行LinearLayout.dispatchTouchEvent方法,因为RelativeLayout与LinearLayout都没有覆写此方法且都是ViewGroup的子类,所以这两个ViewGroup在传递视图的时候都是执行ViewGroup.dispatchTouchEvent。


     下面就先来看看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
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        // 1. 用于测试目,直接忽略
        if (mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
         
        boolean handled = false;
        // 2. 未被其他窗口遮盖
        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.
                // 3. 清理触摸操作的所有痕迹,即派发取消操作
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
             
            // 检测是否拦截Touch Event
            // Check for interception.
            final boolean intercepted;
            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
            {
                // 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.
            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;
            // 如果Touch Event没有取消并且没有被拦截,才会考虑是否向其子视图派发
            if (!canceled && !intercepted)
            {
                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 (childrenCount != 0)
                    {
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final View[] children = mChildren;
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                         
                        // 遍历当前ViewGroup的所有子视图
                        for (int i = childrenCount - 1; i >= 0; i--)
                        {
                            final View child = children[i];
                            // 4 与5
                            if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null))
                            {
                                // 满足以上条件,没必要接收Touch Event
                                continue;
                            }
                             
                            // 如果是在子视图上触摸,经过以上过滤条件,只有当前手指正下方的子视图才会获取到此事件
                            // 子视图是否在TouchTarget中
                            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);
                            // 6. 执行触摸操作(会传递到最终视图的onTouchEvent方法)
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
                            {
                                // Child wants to receive touch within its
                                // bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = i;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                // 会改变mFirstTouchTarget的值
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                     
                    // 没有发现可以接收事件的子视图
                    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;
                    }
                }
            }
             
            // 子视图未消耗Touch Event 或者 被拦截未向子视图派发Touch Event
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null)
            {
                // 由当前ViewGroup处理Touch Event
                // 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);
            }
        }
         
        // 1. 用于测试目,直接忽略
        if (!handled && mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }


1. View.mInputEventConsistencyVerifier

1
2
3
4
5
6
    /**
     * Consistency verifier for debugging purposes.
     * @hide
     */
    protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
            InputEventConsistencyVerifier.isInstrumentationEnabled() ? new InputEventConsistencyVerifier(this0) : null;


2.  View.onFilterTouchEventForSecurity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    /**
     * Filter the touch event to apply security policies.
     
     * @param event
     *            The motion event to be filtered.
     * @return True if the event should be dispatched, false if the event should
     *         be dropped.
     
     * @see #getFilterTouchesWhenObscured
     */
    public boolean onFilterTouchEventForSecurity(MotionEvent event)
    {
        // noinspection RedundantIfStatement
        // 当前被其他窗口遮挡时需要过滤触摸事件。
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
        {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }


3. ViewGroup.cancelAndClearTouchTargets

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
    /**
     * Cancels and clears all touch targets.
     */
    private void cancelAndClearTouchTargets(MotionEvent event)
    {
        // ## 如果指向其他视图,这里进行统一清理
        if (mFirstTouchTarget != null)
        {
            boolean syntheticEvent = false;
            if (event == null)
            {
                // 如果参数为空
                final long now = SystemClock.uptimeMillis();
                // 使用静态方法一个MotionEvent对象,动作为ACTION_CANCEL
                event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                // 输入源是一个触摸屏定位设备。
                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
                syntheticEvent = true;
            }
             
            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next)
            {
                resetCancelNextUpFlag(target.child);
                // 6 向下传递触摸事件(当前传递取消操作)
                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
            }
            // 清理所有触摸目标
            clearTouchTargets();
             
            // 注销拷贝的对象
            if (syntheticEvent)
            {
                event.recycle();
            }
        }
    }


4.canViewReceivePointerEvents

1
2
3
4
5
6
7
8
9
10
    /**
     * Returns true if a child view can receive pointer events.
     
     * @hide
     */
    private static boolean canViewReceivePointerEvents(View child)
    {
        // 当前视图显示或者正在执行动画,才可以接受触摸事件
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null;
    }


5. isTransformedTouchPointInView

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
    /**
     * Returns true if a child view contains the specified point when
     * transformed into its coordinate space. Child must not be null.
     
     * @hide
     */
    protected boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint)
    {
        // 视图有scrollTo或者scrollBy造成的滚动偏移也需要计算在内
        float localX = x + mScrollX - child.mLeft;
        float localY = y + mScrollY - child.mTop;
        if (!child.hasIdentityMatrix() && mAttachInfo != null)
        {
            final float[] localXY = mAttachInfo.mTmpTransformLocation;
            localXY[0] = localX;
            localXY[1] = localY;
            child.getInverseMatrix().mapPoints(localXY);
            localX = localXY[0];
            localY = localXY[1];
        }
        // 触摸点是否在当前子视图内
        final boolean isInView = child.pointInView(localX, localY);
        if (isInView && outLocalPoint != null)
        {
            outLocalPoint.set(localX, localY);
        }
        return isInView;
    }


6. dispatchTransformedTouchEvent

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
    /**
     * 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();
        // 如果是取消动作
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL)
        {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null)
            {
                // 调用View类的方法,其内部会调用View.onTouchEvent
                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)
                {
                    // 调用View类的方法,其内部会调用View.onTouchEvent
                    handled = super.dispatchTouchEvent(event);
                }
                else
                {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);
                     
                    // 如果当前视图通过scrollTo或scrollBy对子视图进行滚动
                    // 向下传递的是x,y偏移mScrollX, mScrollY后的值
                    handled = child.dispatchTouchEvent(event);
                     
                    // 一次手势操作,例如滚动视图,MotionEvent参数都是同一个对象
                    // 如果有疑问的话,可以在onTouchEvent中打印其hashCode看下
                    // 所以这里要对之前做出针对其子视图的偏移就还原,便于之后使用
                    event.offsetLocation(-offsetX, -offsetY);
                }
                // 如果以上消耗了当前触摸事件,直接返回
                return handled;
            }
            // 获取当前参数的拷贝,对其做得修改不会影响当前参数对象
            transformedEvent = MotionEvent.obtain(event);
        }
        else
        {
            transformedEvent = event.split(newPointerIdBits);
        }
         
        // 与以上操作雷同,不具体解释
        // newPointerIdBits != oldPointerIdBits ?1
        // 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;
    }


三、View并且非ViewGroup子类,如何分发事件?

 图片是点击绿色按钮,并显示出相应的LOG,并且与文章顶部的图片是对应的

wKioL1QjhgnAEJuiAABKDf6H_xM297.gif

    之前分析了ViewGroup是如何分发事件的,可以解释RelativeLayout与LinearLayout如何分发事件的,下面看下View.dispatchTouchEvent的源码,可以了解下View或者ViewGroup(其中可以调用super.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
    /**
     * 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)
    {
        // 用于测试目,直接忽略
        if (mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
         
        // 上面代码的第2个标注。 未被其他窗口遮盖
        if (onFilterTouchEventForSecurity(event))
        {
            // noinspection SimplifiableIfStatement
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event))
            {
                // 如果有监听器,执行监听器的,不在执行当前视图的onTouchEvent方法
                return true;
            }
            // 执行当前视图的onTouchEvent方法
            if (onTouchEvent(event))
            {
                return true;
            }
        }
         
        // 用于测试目,直接忽略
        if (mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }


四、事件分发简单总结

dispatchTouchEvent
事件派发显示隧道方式、再是冒泡方式
隧道方式传递,直道某一个元素消耗此事件,由上至下逐层分发视图。
冒泡方式传递,当某个视图消耗事件后其return boolean 是与分发相反的方法向上传递。
具体分发给哪一个视图是通过当前触摸点坐标在当前层哪个视图上判断


onInterceptTouchEvent
ViewGroup的方法,如果当前ViewGroup需要拦截传递给其子视图的事件,需要return true






本文转自 glblong 51CTO博客,原文链接:http://blog.51cto.com/glblong/1558006,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10062 0
《深入解析Android 虚拟机》——1.2 分析Android源码结构
无论是Android 1.5还是Android 4.3和Android L,各个版本的源码目录基本类似。在里面包含了原始Android的目标机代码、主机编译工具和仿真环境。解压缩下载的Android 4.3源码包后,第一级别目录结构的具体说明如表1-1所示。
1033 0
android(cm11)状态栏源码分析(一)
版权声明:您好,转载请留下本人博客的地址,谢谢 https://blog.csdn.net/hongbochen1223/article/details/50216563 (一):写在前面 最近由于工作需要,需要了解CM11中的有关于StatusBar相关的内容。
950 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13875 0
+关注
20382
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载