【转】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( this 0 ) :  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,如需转载请自行联系原作者

目录
相关文章
|
5月前
|
Android开发
39. 【Android教程】触摸事件分发
39. 【Android教程】触摸事件分发
43 2
|
6月前
|
Android开发 容器
[Android]View的事件分发机制(源码解析)
[Android]View的事件分发机制(源码解析)
60 0
|
6月前
|
XML Java Android开发
Android App开发触摸事件中手势事件Event的分发流程讲解与实战(附源码 简单易懂)
Android App开发触摸事件中手势事件Event的分发流程讲解与实战(附源码 简单易懂)
100 0
|
Android开发 开发者
Android View 事件分发机制,看这一篇就够了(二)
Android View 事件分发机制,看这一篇就够了
|
Android开发
Android View 事件分发机制,看这一篇就够了(一)
Android View 事件分发机制,看这一篇就够了
|
Android开发 开发者 容器
Android事件分发机制
Android事件分发机制
|
XML Android开发 数据格式
Android 了解View的事件分发详解
Android 了解View的事件分发详解
82 0
|
监控 Android开发 索引
“framework必会”系列:Android Input系统(二)事件分发机制
对于目前应用开发已经饱和的大环境下,作为一个多年Android开发,逼迫我们Android开发往更深层次的framework层走,于是就有了这么个系列
|
4天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
5天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。