Android高级UI开发(三十二)事件分发2:ViewGroup事件分发机制

举报
yd_57386892 发表于 2020/12/29 01:39:12 2020/12/29
【摘要】 上一篇我们讲了View的事件分发,我们回忆一下当时总结的结论:  View事件执行流程 dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick    2.dispatchTouchEvent的返回值对事件分发的影响 当返回true时,后续的事件,该Vie...

上一篇我们讲了View的事件分发,我们回忆一下当时总结的结论:

  1.  View事件执行流程

dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick

   2.dispatchTouchEvent的返回值对事件分发的影响

当返回true时,后续的事件,该View还会继续接收到,如Down---Up事件(Down事件已被消费,后续的Up就会继续接收到)

当返回false时,后续的事件,该View再也不能接收到,如Down---Up事件(接收完Down事件后,Up不会再分发给该VIEW,而是把Down事件转发给父容器ViewGroup,如果条件满足,UP事件以后也由ViewGroup接收,从而ViewGroup实现了单击函数)。

回忆着2点总结的原因是:ViewGroup的事件分发与View大部分都是雷同的。唯一的区别是ViewGroup多了一个

public boolean onInterceptTouchEvent(MotionEvent event) 函数,那我们今天研究ViewGroup的事件分发就从onInterceptTouchEvent函数说起。

配套Demo实验源码:https://download.csdn.net/download/gaoxiaoweiandy/11164333

1.    boolean onInterceptTouchEvent(MotionEvent event) 

这个函数通常返回false,表示不拦截事件,言外之意是ViewGroup接收到事件后,会继续传递给它嵌套包含的child视图,通常是一个View。相反,如果返回true,表示拦截,不会再将触摸事件传递给子View,而是由ViewGruop自己处理。这里透漏一下,通常这个函数总是返回false,不拦截。

1.1 返回false(默认总是返回false)

举个例子,有如下一个布局文件:


  
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:id="@+id/layout"
  5. >
  6. <Button
  7. android:onClick="buttonClick"
  8. android:id="@+id/button1"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:layout_marginTop="14dp"
  12. android:text="Button" />
  13. </RelativeLayout>

      其中的RelativeLayout就是一个ViewGroup,它包含一个chlid子View:Button。   当我们点击Button的时候,DOWN事件先由RelativeLayout接收到,从ViewGroup类的dispatchTouchEvent函数开始,在ViewGroup.dispatchTouchEvent内部调用的onInterceptTouchEvent函数返回false(一般都返回false,后面分析源码再证实),所以RelativeLayout继续将Down事件传递给子View:Button,接下来开始传给Button的dispatchTouchEvent函数(实质是View类的),接下来的执行过程就和我们上一篇分析的View的分发机制一样了,Button最后依次执行了dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。而ViewGroup并没有执行后面的   onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。 所以点击Button时,ViewGroup不会响应单击函数onClick的,因为它将事件传递给了Button,同时Button的dispatchTouchEvent默认返回true,因此后续的UP事件也由Button消费

 

 

 

 

1.2 返回true

布局不变,但是事件被ViewGroup拦截了,当点击Button的时候,DOWN事件同样先由RelativeLayout接收到,从ViewGroup类的dispatchTouchEvent函数开始,让在ViewGroup.dispatchTouchEvent内部调用的onInterceptTouchEvent函数返回true(可以重写onInterceptTouchEvent强制让它返回true),所以RelativeLayout将事件拦截了下来,不再往下传递给子View:Button了。它自己消费这些事件,最后的结果是ViewGroup执行了dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick这个过程(Tip:这里和View的分发过程又一样了,是因为ViewGroup自己处理事件时调用了super.dispatchTouchEvent,它的super正是View).产生了一个奇怪的效果:我们明明点击的是Button,并没有点击RelativeLayout,但最终RelativeLayout却响应了单击函数。

 

2.  证明上面的结论

我们还是通过写一个小Demo,通过做实验(改变onInterceptTouchEvent的返回值)且结合ViewGroup源码来得出上面的结论。

2.1  onInterceptTouchEvent返回false

ViewGroup会将事件传递给子View:MyButton。

Demo的布局文件:


  
  1. <com.example.relativelayoutevent.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:id="@+id/layout"
  5. >
  6. <com.example.relativelayoutevent.MyButton
  7. android:onClick="buttonClick"
  8. android:id="@+id/button1"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:layout_marginTop="14dp"
  12. android:text="Button" />
  13. </com.example.relativelayoutevent.MyLayout>

MainActivity.java


  
  1. package com.example.relativelayoutevent;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.widget.Button;
  9. import android.widget.ImageView;
  10. import android.widget.RelativeLayout;
  11. public class MainActivity extends Activity implements OnClickListener, View.OnTouchListener {
  12. private MyButton myButton;
  13. private MyLayout myLayout;
  14. private ImageView imvTest;
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. myButton = (MyButton)findViewById(R.id.button1);
  20. myLayout = (MyLayout)findViewById(R.id.layout);
  21. myLayout.setOnTouchListener(this);
  22. myLayout.setOnClickListener(this);
  23. myButton.setOnClickListener(this);
  24. myButton.setOnTouchListener(this);
  25. }
  26. @Override
  27. public boolean onTouch(View v, MotionEvent event) {
  28. boolean returenOnTouch = false;
  29. Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+" ,result="+returenOnTouch+"----view:"+v);
  30. return returenOnTouch;
  31. }
  32. @Override
  33. public void onClick(View v) {
  34. Log.i("eventTest", "OnClickListener----view:"+v);
  35. }
  36. }

这里分别为RelativeLayout与Button定义了单击响应函数和onTouchListener.onTouch并且返回false,以便能执行到onClick那里,至于为何,在上一篇已经总结过了:dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。

MyLayout.java


  
  1. package com.example.relativelayoutevent;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.widget.RelativeLayout;
  7. public class MyLayout extends RelativeLayout {
  8. public MyLayout(Context context) {
  9. super(context);
  10. }
  11. public MyLayout(Context context, AttributeSet attrs) {
  12. super(context, attrs);
  13. }
  14. public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  15. super(context, attrs, defStyleAttr);
  16. }
  17. @Override
  18. public boolean onTouchEvent(MotionEvent event) {
  19. boolean onTouchEventResult = super.onTouchEvent(event);
  20. Log.i("eventTest", "MyLayoutonTouchEvent:action--"+event.getAction()+
  21. ",result="+onTouchEventResult+",view:MyLayout");
  22. return onTouchEventResult;//默認返回true
  23. }
  24. @Override
  25. public boolean dispatchTouchEvent(MotionEvent event) {
  26. boolean dispatchResult = super.dispatchTouchEvent(event); //默认返回true
  27. Log.i("eventTest", "MyLayoutdispatchTouch:action--"+event.getAction()+
  28. ",result="+dispatchResult+",view:MyLayout");
  29. return dispatchResult;
  30. }
  31. @Override
  32. public boolean onInterceptTouchEvent(MotionEvent event) {
  33. boolean onInterceptResult = super.onInterceptTouchEvent(event); //默认返回false
  34. onInterceptResult = false;
  35. Log.i("eventTest", "MyInterceptTouch:action--"+event.getAction()+
  36. ",result="+onInterceptResult+",view:MyLayout");
  37. return onInterceptResult;
  38. }
  39. }

这里我们自定义了RelativieyLayout:MyLayout重写了

dispatchTouchEvent  默认返回true,就是说super.dispatchTouchEvent 返回true, Down与后续的UP事件可以顺利接收.
 
onTouchEvent 默认返回true,就是说super.onTouchEvent  返回true,从而dispatchTouchEvent 返回true.   
 

  
  1. onInterceptTouchEvent 默认返回false,就是说super.onInterceptTouchEvent 返回false.默认不拦截事件,将事件交给子View:MyButton。
  2. 这里我们显示的将onInterceptResult 指定为false,是为了直观的知道它默认返回false,并且后面做实验时可以随时将返回值改为true.

MyButton.java


  
  1. package com.example.relativelayoutevent;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.widget.Button;
  7. public class MyButton extends Button {
  8. public MyButton(Context context, AttributeSet attrs) {
  9. super(context, attrs);
  10. // TODO Auto-generated constructor stub
  11. }
  12. @Override
  13. public boolean dispatchTouchEvent(MotionEvent event) {
  14. boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
  15. Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
  16. ",result="+dispatchResult+",view:MyButton");
  17. return dispatchResult;
  18. }
  19. @Override
  20. public boolean onTouchEvent(MotionEvent event) {
  21. boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
  22. Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
  23. ",result="+onTouchEventResult+",view:MyButton");
  24. return onTouchEventResult;
  25. }
  26. }

这里我们自定义了Button:MyButton重写了

dispatchTouchEvent  默认返回true,就是说super.dispatchTouchEvent 返回true, Down与后续的UP事件可以顺利接收.
 
onTouchEvent 默认返回true,就是说super.onTouchEvent  返回true,从而dispatchTouchEvent 返回true.   
 

  
  1. Tip: View是没有“onInterceptTouchEvent" 这个函数的
  2. Ok,我们现在试试点击Button看一下会打印出什么日志:

  
  1. eventTest: MyInterceptTouch:action--0,result=false,view:MyLayout
  2. eventTest: onTouchListener:acton--0 ,result=false----view:com.example.relativelayoutevent.MyButton
  3. eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
  4. eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
  5. eventTest: MyLayoutdispatchTouch:action--0,result=true,view:MyLayout
  6. eventTest: MyInterceptTouch:action--1,result=false,view:MyLayout
  7. eventTest: onTouchListener:acton--1 ,result=false,view:MyButton
  8. eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
  9. eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
  10. eventTest: MyLayoutdispatchTouch:action--1,result=true,view:MyLayout
  11. eventTest: OnClickListener----view:com.example.relativelayoutevent.MyButton

同时我们贴出ViewGroup的源码


  
  1. @Override
  2. public boolean dispatchTouchEvent(MotionEvent ev) {
  3. if (mInputEventConsistencyVerifier != null) {
  4. mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
  5. }
  6. // If the event targets the accessibility focused view and this is it, start
  7. // normal event dispatch. Maybe a descendant is what will handle the click.
  8. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
  9. ev.setTargetAccessibilityFocus(false);
  10. }
  11. boolean handled = false;
  12. if (onFilterTouchEventForSecurity(ev)) {
  13. final int action = ev.getAction();
  14. final int actionMasked = action & MotionEvent.ACTION_MASK;
  15. // Handle an initial down.
  16. if (actionMasked == MotionEvent.ACTION_DOWN) {
  17. // Throw away all previous state when starting a new touch gesture.
  18. // The framework may have dropped the up or cancel event for the previous gesture
  19. // due to an app switch, ANR, or some other state change.
  20. cancelAndClearTouchTargets(ev);
  21. resetTouchState();
  22. }
  23. // Check for interception.
  24. final boolean intercepted;
  25. if (actionMasked == MotionEvent.ACTION_DOWN
  26. || mFirstTouchTarget != null) {
  27. final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  28. if (!disallowIntercept) {
  29. intercepted = onInterceptTouchEvent(ev);
  30. ev.setAction(action); // restore action in case it was changed
  31. } else {
  32. intercepted = false;
  33. }
  34. } else {
  35. // There are no touch targets and this action is not an initial down
  36. // so this view group continues to intercept touches.
  37. intercepted = true;
  38. }
  39. // If intercepted, start normal event dispatch. Also if there is already
  40. // a view that is handling the gesture, do normal event dispatch.
  41. if (intercepted || mFirstTouchTarget != null) {
  42. ev.setTargetAccessibilityFocus(false);
  43. }
  44. // Check for cancelation.
  45. final boolean canceled = resetCancelNextUpFlag(this)
  46. || actionMasked == MotionEvent.ACTION_CANCEL;
  47. // Update list of touch targets for pointer down, if needed.
  48. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
  49. TouchTarget newTouchTarget = null;
  50. boolean alreadyDispatchedToNewTouchTarget = false;
  51. if (!canceled && !intercepted) {
  52. // If the event is targeting accessibility focus we give it to the
  53. // view that has accessibility focus and if it does not handle it
  54. // we clear the flag and dispatch the event to all children as usual.
  55. // We are looking up the accessibility focused host to avoid keeping
  56. // state since these events are very rare.
  57. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
  58. ? findChildWithAccessibilityFocus() : null;
  59. if (actionMasked == MotionEvent.ACTION_DOWN
  60. || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
  61. || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
  62. final int actionIndex = ev.getActionIndex(); // always 0 for down
  63. final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
  64. : TouchTarget.ALL_POINTER_IDS;
  65. // Clean up earlier touch targets for this pointer id in case they
  66. // have become out of sync.
  67. removePointersFromTouchTargets(idBitsToAssign);
  68. final int childrenCount = mChildrenCount;
  69. if (newTouchTarget == null && childrenCount != 0) {
  70. final float x = ev.getX(actionIndex);
  71. final float y = ev.getY(actionIndex);
  72. // Find a child that can receive the event.
  73. // Scan children from front to back.
  74. final ArrayList<View> preorderedList = buildTouchDispatchChildList();
  75. final boolean customOrder = preorderedList == null
  76. && isChildrenDrawingOrderEnabled();
  77. final View[] children = mChildren;
  78. for (int i = childrenCount - 1; i >= 0; i--) {
  79. final int childIndex = getAndVerifyPreorderedIndex(
  80. childrenCount, i, customOrder);
  81. final View child = getAndVerifyPreorderedView(
  82. preorderedList, children, childIndex);
  83. // If there is a view that has accessibility focus we want it
  84. // to get the event first and if not handled we will perform a
  85. // normal dispatch. We may do a double iteration but this is
  86. // safer given the timeframe.
  87. if (childWithAccessibilityFocus != null) {
  88. if (childWithAccessibilityFocus != child) {
  89. continue;
  90. }
  91. childWithAccessibilityFocus = null;
  92. i = childrenCount - 1;
  93. }
  94. if (!canViewReceivePointerEvents(child)
  95. || !isTransformedTouchPointInView(x, y, child, null)) {
  96. ev.setTargetAccessibilityFocus(false);
  97. continue;
  98. }
  99. newTouchTarget = getTouchTarget(child);
  100. if (newTouchTarget != null) {
  101. // Child is already receiving touch within its bounds.
  102. // Give it the new pointer in addition to the ones it is handling.
  103. newTouchTarget.pointerIdBits |= idBitsToAssign;
  104. break;
  105. }
  106. resetCancelNextUpFlag(child);
  107. if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
  108. // Child wants to receive touch within its bounds.
  109. mLastTouchDownTime = ev.getDownTime();
  110. if (preorderedList != null) {
  111. // childIndex points into presorted list, find original index
  112. for (int j = 0; j < childrenCount; j++) {
  113. if (children[childIndex] == mChildren[j]) {
  114. mLastTouchDownIndex = j;
  115. break;
  116. }
  117. }
  118. } else {
  119. mLastTouchDownIndex = childIndex;
  120. }
  121. mLastTouchDownX = ev.getX();
  122. mLastTouchDownY = ev.getY();
  123. newTouchTarget = addTouchTarget(child, idBitsToAssign);
  124. alreadyDispatchedToNewTouchTarget = true;
  125. break;
  126. }
  127. // The accessibility focus didn't handle the event, so clear
  128. // the flag and do a normal dispatch to all children.
  129. ev.setTargetAccessibilityFocus(false);
  130. }
  131. if (preorderedList != null) preorderedList.clear();
  132. }
  133. if (newTouchTarget == null && mFirstTouchTarget != null) {
  134. // Did not find a child to receive the event.
  135. // Assign the pointer to the least recently added target.
  136. newTouchTarget = mFirstTouchTarget;
  137. while (newTouchTarget.next != null) {
  138. newTouchTarget = newTouchTarget.next;
  139. }
  140. newTouchTarget.pointerIdBits |= idBitsToAssign;
  141. }
  142. }
  143. }
  144. // Dispatch to touch targets.
  145. if (mFirstTouchTarget == null) {
  146. // No touch targets so treat this as an ordinary view.
  147. handled = dispatchTransformedTouchEvent(ev, canceled, null,
  148. TouchTarget.ALL_POINTER_IDS);
  149. } else {
  150. // Dispatch to touch targets, excluding the new touch target if we already
  151. // dispatched to it. Cancel touch targets if necessary.
  152. TouchTarget predecessor = null;
  153. TouchTarget target = mFirstTouchTarget;
  154. while (target != null) {
  155. final TouchTarget next = target.next;
  156. if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
  157. handled = true;
  158. } else {
  159. final boolean cancelChild = resetCancelNextUpFlag(target.child)
  160. || intercepted;
  161. if (dispatchTransformedTouchEvent(ev, cancelChild,
  162. target.child, target.pointerIdBits)) {
  163. handled = true;
  164. }
  165. if (cancelChild) {
  166. if (predecessor == null) {
  167. mFirstTouchTarget = next;
  168. } else {
  169. predecessor.next = next;
  170. }
  171. target.recycle();
  172. target = next;
  173. continue;
  174. }
  175. }
  176. predecessor = target;
  177. target = next;
  178. }
  179. }
  180. // Update list of touch targets for pointer up or cancel, if needed.
  181. if (canceled
  182. || actionMasked == MotionEvent.ACTION_UP
  183. || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
  184. resetTouchState();
  185. } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
  186. final int actionIndex = ev.getActionIndex();
  187. final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
  188. removePointersFromTouchTargets(idBitsToRemove);
  189. }
  190. }
  191. if (!handled && mInputEventConsistencyVerifier != null) {
  192. mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
  193. }
  194. return handled;
  195. }

 

我们日志一行一行来解析:

第1行,DOWN事件(action = 0)进入ViewGroup的dispatchTouchEvent内部,先调用了onInterceptTouchEvent(关键信息已用红色标注),结果onInterceptTouchEvent返回false,不拦截事件。为什么说onInterceptTouchEvent默认会返回false,我们看一下onInterceptTouchEvent的代码:


  
  1. public boolean onInterceptTouchEvent(MotionEvent ev) {
  2. if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
  3. && ev.getAction() == MotionEvent.ACTION_DOWN
  4. && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
  5. && isOnScrollbarThumb(ev.getX(), ev.getY())) {
  6. return true;
  7. }
  8. return false;
  9. }
首先if语句的第一个条件是事件来源于InputDevice.SOURCE_MOUSE(鼠标),因此onInterceptTouchEvent肯定不会返回true,在手机上用手指点肯定返回的是false。
 

 

为了阅读方便,还是把日志复制一份在这里。


  
  1. eventTest: MyInterceptTouch:action--0,result=false,view:MyLayout
  2. eventTest: onTouchListener:acton--0 ,result=false----view:com.example.relativelayoutevent.MyButton
  3. eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
  4. eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
  5. eventTest: MyLayoutdispatchTouch:action--0,result=true,view:MyLayout
  6. eventTest: MyInterceptTouch:action--1,result=false,view:MyLayout
  7. eventTest: onTouchListener:acton--1 ,result=false,view:MyButton
  8. eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
  9. eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
  10. eventTest: MyLayoutdispatchTouch:action--1,result=true,view:MyLayout
  11. eventTest: OnClickListener----view:com.example.relativelayoutevent.MyButton

 

第2行日志:将事件传递给子View的dispatchTouchEvent:

         第1行日志已表示ViewGroup不拦截事件,而是继续将DOWN事件(action= 0 )传递给子View(MyButton),于是从ViewGroup.dispatchTouchEvent 内部调用了 child.dispatchTouchEvent,即(MyButton extends View)  view.dispatchTouchEvent,于是接下来的流程就和View事件分发一样了,先执行onTouchListener--onTouch,这里我们在MainActivity里已经强制返回false,以便能执行onTouchEvent,同时打印出了第2行日志。

第3行日志:执行MyButton的onTouchEvent,打印出日志。

第4行,MyButton的dispatchTouchEvent内部执行完onTouchEvent后,递归返回后打印出了日志。

第5行,MyLayout的dispatchTouchEvent,连续调用1,2,3,4步,递归返回后打印出了日志。  

    其实从第2行日志-----第4行日志 都是子view的dispatchTouchEvent在处理事件,我们来看一下MyLayout的dispatchTouchEvent是在哪里将事件传递给了子View的dispatchTouchEvent;请翻阅到ViewGroup源码,我已用红色标注:

for (int i = childrenCount - 1; i >= 0; i--) {

(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)

........

}

  这个for循环遍历ViewGroup内部的各个子View,判断哪一个被点击了,如果被点击了就执行dispatchTransformedTouchEvent,而这个函数里又调用了child.dispatchTouchEvent,即(MyButton)View的事件分发将要开始执行:dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。dispatchTransformedTouchEvent函数的源码如下:


  
  1. private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
  2. View child, int desiredPointerIdBits) {
  3. final boolean handled;
  4. // Canceling motions is a special case. We don't need to perform any transformations
  5. // or filtering. The important part is the action, not the contents.
  6. final int oldAction = event.getAction();
  7. if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
  8. event.setAction(MotionEvent.ACTION_CANCEL);
  9. if (child == null) {
  10. handled = super.dispatchTouchEvent(event);
  11. } else {
  12. handled = child.dispatchTouchEvent(event);
  13. }
  14. event.setAction(oldAction);
  15. return handled;
  16. }
  17. 。。。。。。
  18. }

内部调用了handled = child.dispatchTouchEvent(event); 而handler会默认为true, 因此dispatchTransformedTouchEvent返回true,最终也会使ViewGroup.dispatchTouchEvent会默认返回true.

 

第6行至第10行日志:UP事件又将上述过程1-5重复了一遍,在此不再赘述。

onInterceptTouchEvent返回false时,DOWN事件与UP事件最终都传递给了MyButton去处理,所以MyButton的onClick得以执行,而RelativeLayout的onClick不会执行。

 

2.2 onInterceptTouchEvent返回true

其它代码不变,只 修改MyLayout的onInterceptTouchEvent,让其返回true.表示拦截事件,这时我们再点击Button试试。

MyLayout代码及点击Button时的日志如下:

MyLayout.java:


  
  1. package com.example.relativelayoutevent;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.widget.RelativeLayout;
  7. public class MyLayout extends RelativeLayout {
  8. public MyLayout(Context context) {
  9. super(context);
  10. }
  11. public MyLayout(Context context, AttributeSet attrs) {
  12. super(context, attrs);
  13. }
  14. public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  15. super(context, attrs, defStyleAttr);
  16. }
  17. @Override
  18. public boolean onTouchEvent(MotionEvent event) {
  19. boolean onTouchEventResult = super.onTouchEvent(event);
  20. Log.i("eventTest", "MyLayoutonTouchEvent:action--"+event.getAction()+
  21. ",result="+onTouchEventResult+",view:MyLayout");
  22. return onTouchEventResult;//默認返回true
  23. }
  24. @Override
  25. public boolean dispatchTouchEvent(MotionEvent event) {
  26. boolean dispatchResult = super.dispatchTouchEvent(event); //默认返回true
  27. Log.i("eventTest", "MyLayoutdispatchTouch:action--"+event.getAction()+
  28. ",result="+dispatchResult+",view:MyLayout");
  29. return dispatchResult;
  30. }
  31. @Override
  32. public boolean onInterceptTouchEvent(MotionEvent event) {
  33. boolean onInterceptResult = super.onInterceptTouchEvent(event); //默认返回false
  34. onInterceptResult = true;
  35. Log.i("eventTest", "MyInterceptTouch:action--"+event.getAction()+
  36. ",result="+onInterceptResult+",view:MyLayout");
  37. return onInterceptResult;
  38. }
  39. }

让ViewGroup的onInterceptTouchEvent返回false.

点击Button时的日志如下:


  
  1. eventTest: MyInterceptTouch:action--0,result=true,view:MyLayout
  2. eventTest: onTouchListener:acton--0 ,result=false----view:com.example.relativelayoutevent.MyLayout
  3. eventTest: MyLayoutonTouchEvent:action--0,result=true,view:MyLayout
  4. eventTest: MyLayoutdispatchTouch:action--0,result=true,view:MyLayout
  5. eventTest: onTouchListener:acton--1 ,result=false----view:com.example.relativelayoutevent.MyLayout
  6. eventTest: MyLayoutonTouchEvent:action--1,result=true,view:MyLayout
  7. eventTest: MyLayoutdispatchTouch:action--1,result=true,view:MyLayout
  8. eventTest: OnClickListener----view:com.example.relativelayoutevent.MyLayout

 

从日志上观察,点击MyButton时,竟然MyLayout响应了单击函数,也就是说界面空白处响应了onClick!

好,我们还是来一步一步分析日志:

第1行日志:

DOWN事件(action = 0)进入ViewGroup的dispatchTouchEvent内部,先调用了onInterceptTouchEvent(关键信息已用红色标注),结果onInterceptTouchEvent返回true,拦截截事件,MyButton将不处理DOWN事件,而是MyLayout自己处理。于是打印出了第2行日志。

第2行日志:DOWN事件由MyLayout自己处理,所以先执行了MyLayout的onTouchListener.onTouch,我们在这里强行在MainActivity中返回false.以便MyLayout能执行到onTouchEvent.。

 

第3行日志:执行MyLayout的onTouchEvent,打印出日志。

第4行,MyLayout的dispatchTouchEvent内部执行完onTouchEvent后,递归返回后打印出了日志。

我们发现从1-4,DOWN事件一直由MyLayout(ViewGroup)来处理,而且处理流程与View的事件分发一样:

dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent

也就是说这时可以把MyLayout(ViewGroup)看作一个View了。为何会这样,我们还是翻上去看一下ViewGroup源,上面已经用红色标注:


  
  1. // Dispatch to touch targets.
  2. if (mFirstTouchTarget == null) {
  3. // No touch targets so treat this as an ordinary view.
  4. handled = dispatchTransformedTouchEvent(ev, canceled, null,
  5. TouchTarget.ALL_POINTER_IDS);
  6. }

这个就是子View没有处理DOWN事件,mFirstTouchTarget 就是null,然后执行

dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);这次第三个参数child被赋值了null,所以dispatchTransformedTouchEvent最终会执行super.dispatchTouchEvent, ViewGroup的super就是View。所以后续把ViewGroup的事件分发按照View的事件分发来执行,然后就执行了MyLayout的onTouchListener.onTouc与MyLayout的onTouchListener.onTouchEvent。

Tip:mFirstTouchTarget 是在这行代码 : newTouchTarget = addTouchTarget(child, idBitsToAssign); 中的addTouchTarget函数内部被赋值的,这行代码的执行恰好是在子View成功处理了Down事件的条件下执行的。

 

第5,6,7行日志是UP事件的日志,同上面的Down事件一样,都交由ViewGroup来执行,最终还是走的是View的事件分发流程。只是,只是UP事件少调用了一个onInterceptTouchEvent函数,因为在Down事件的时候已经return true表示拦截事件,那么UP事件就不用再调用了,随之也是被ViewGroup拦截。

 

到此,我们就分析完ViewGroup的事件分发机制了。配套Demo实验源码:https://download.csdn.net/download/gaoxiaoweiandy/11164333

 

 

 

 

 

 

 

 

 

文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/89882161

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。