查看: 344|回复: 0

[Android教程] Android6.0源码解读之ViewGroup点击事件分发机制

发表于 2018-4-12 08:00:03

本篇博文是Android点击事件分发机制系列博文的第三篇,主要是从解读ViewGroup类的源码入手,根据源码理清ViewGroup点击事件分发原理,明白ViewGroup和View点击事件分发的关系,并掌握ViewGroup点击事件分法机制。特别声明的是,本源码解读是基于最新的Android6.0版本。

各位童鞋可以参考下面链接进行系统学习

(一)Android6.0触摸事件分发机制解读

(二)Android6.0源码解读之View点击事件分发机制

(三)Android6.0源码解读之ViewGroup点击事件分发机制

(四)Android6.0源码解读之Activity点击事件分发机制

ViewGroup事件分发中的三个重要方法的源码解析

关于ViewGroup事件分发,我们重点需要解读dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法。ViewGroup比View多了一个onInterceptTouchEvent拦截事件方法,该方法源码默认返回false,即ViewGroup默认不拦截任何事件。

(一)dispatchTouchEvent源码解析
  1. /**
  2. * 重写了父类View的dispatchTouchEvent方法
  3. */
  4. @Override
  5. public boolean dispatchTouchEvent(MotionEvent ev) {
  6. if (mInputEventConsistencyVerifier != null) {
  7. mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
  8. }
  9. // If the event targets the accessibility focused view and this is it, start
  10. // normal event dispatch. Maybe a descendant is what will handle the click.
  11. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
  12. ev.setTargetAccessibilityFocus(false);
  13. }
  14. boolean handled = false;// 是否处理
  15. if (onFilterTouchEventForSecurity(ev)) {
  16. final int action = ev.getAction();
  17. final int actionMasked = action & MotionEvent.ACTION_MASK;
  18. // 手指按下去进行一些初始化的处理
  19. if (actionMasked == MotionEvent.ACTION_DOWN) {
  20. // 当开启了一个新的手势触摸时,我要先去重置之前的状态
  21. // Throw away all previous state when starting a new touch gesture.
  22. // The framework may have dropped the up or cancel event for the previous gesture
  23. // due to an app switch, ANR, or some other state change.
  24. cancelAndClearTouchTargets(ev);// 取消、清理
  25. resetTouchState();// 重置
  26. }
  27. // Check for interception.
  28. final boolean intercepted;// 是否被拦截
  29. // 如果事件类型ACTION_DOWN或者mFirstTouchTarget不为空
  30. if (actionMasked == MotionEvent.ACTION_DOWN
  31. || mFirstTouchTarget != null) {
  32. final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  33. if (!disallowIntercept) {// 当disallowIntercept为false时
  34. intercepted = onInterceptTouchEvent(ev);// 这里是重点,它会调用onInterceptTouchEvent方法,当该方法为true时拦截,为false时不拦截
  35. // 所谓的拦截,是指按下去自身以及以后的后续事件move up,拦截下来给自己onTouch使用
  36. ev.setAction(action); // restore action in case it was changed
  37. } else {
  38. intercepted = false;
  39. }
  40. } else {
  41. // There are no touch targets and this action is not an initial down
  42. // so this view group continues to intercept touches.
  43. intercepted = true;
  44. }
  45. // If intercepted, start normal event dispatch. Also if there is already
  46. // a view that is handling the gesture, do normal event dispatch.
  47. if (intercepted || mFirstTouchTarget != null) {
  48. ev.setTargetAccessibilityFocus(false);
  49. }
  50. // Check for cancelation.
  51. final boolean canceled = resetCancelNextUpFlag(this)
  52. || actionMasked == MotionEvent.ACTION_CANCEL;
  53. // Update list of touch targets for pointer down, if needed.
  54. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
  55. TouchTarget newTouchTarget = null;
  56. boolean alreadyDispatchedToNewTouchTarget = false;
  57. if (!canceled && !intercepted) {// 这里的canceled和intercepted都为false时,条件成立,也就是说不拦截
  58. // If the event is targeting accessiiblity focus we give it to the
  59. // view that has accessibility focus and if it does not handle it
  60. // we clear the flag and dispatch the event to all children as usual.
  61. // We are looking up the accessibility focused host to avoid keeping
  62. // state since these events are very rare.
  63. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
  64. ? findChildWithAccessibilityFocus() : null;
  65. if (actionMasked == MotionEvent.ACTION_DOWN
  66. || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
  67. || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {// 按下去或多指触摸或者移动划过
  68. final int actionIndex = ev.getActionIndex(); // always 0 for down
  69. final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
  70. : TouchTarget.ALL_POINTER_IDS;
  71. // Clean up earlier touch targets for this pointer id in case they
  72. // have become out of sync.
  73. removePointersFromTouchTargets(idBitsToAssign);
  74. final int childrenCount = mChildrenCount;
  75. if (newTouchTarget == null && childrenCount != 0) {// 如果子控件的个数不为0 且 newTouchTarget为空
  76. final float x = ev.getX(actionIndex);
  77. final float y = ev.getY(actionIndex);
  78. // Find a child that can receive the event.
  79. // Scan children from front to back.
  80. // 考虑到两个View交叉重合的情况,下面的先放进集合,但是按常理说我们手指先按到上面的,这里做了一个倒序
  81. final ArrayList<View> preorderedList = buildOrderedChildList();
  82. final boolean customOrder = preorderedList == null
  83. && isChildrenDrawingOrderEnabled();
  84. final View[] children = mChildren;
  85. for (int i = childrenCount - 1; i >= 0; i--) {// 遍历ViewGroup中的子控件
  86. final int childIndex = customOrder
  87. ? getChildDrawingOrder(childrenCount, i) : i;// 得到子控件绘画的顺序
  88. final View child = (preorderedList == null)
  89. ? children[childIndex] : preorderedList.get(childIndex);
  90. // If there is a view that has accessibility focus we want it
  91. // to get the event first and if not handled we will perform a
  92. // normal dispatch. We may do a double iteration but this is
  93. // safer given the timeframe.
  94. if (childWithAccessibilityFocus != null) {
  95. if (childWithAccessibilityFocus != child) {
  96. continue;
  97. }
  98. childWithAccessibilityFocus = null;
  99. i = childrenCount - 1;
  100. }
  101. if (!canViewReceivePointerEvents(child)
  102. || !isTransformedTouchPointInView(x, y, child, null)) {
  103. ev.setTargetAccessibilityFocus(false);
  104. continue;
  105. }
  106. newTouchTarget = getTouchTarget(child);
  107. if (newTouchTarget != null) {
  108. // Child is already receiving touch within its bounds.
  109. // Give it the new pointer in addition to the ones it is handling.
  110. newTouchTarget.pointerIdBits |= idBitsToAssign;
  111. break;
  112. }
  113. resetCancelNextUpFlag(child);
  114. // 判断是否有子控件,如果没有就不会执行里面的操作,如果有子控件则执行内部的操作
  115. if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
  116. // Child wants to receive touch within its bounds.
  117. mLastTouchDownTime = ev.getDownTime();
  118. if (preorderedList != null) {
  119. // childIndex points into presorted list, find original index
  120. for (int j = 0; j < childrenCount; j++) {
  121. if (children[childIndex] == mChildren[j]) {
  122. mLastTouchDownIndex = j;
  123. break;
  124. }
  125. }
  126. } else {
  127. mLastTouchDownIndex = childIndex;
  128. }
  129. mLastTouchDownX = ev.getX();// 拿到X、Y位置
  130. mLastTouchDownY = ev.getY();
  131. // newTouchTarget用到了单向链表
  132. newTouchTarget = addTouchTarget(child, idBitsToAssign);// 找到最终触摸的对象
  133. alreadyDispatchedToNewTouchTarget = true;// 已经分发给一个新触摸的对象
  134. break;
  135. }
  136. // The accessibility focus didn't handle the event, so clear
  137. // the flag and do a normal dispatch to all children.
  138. ev.setTargetAccessibilityFocus(false);
  139. }
  140. if (preorderedList != null) preorderedList.clear();
  141. }
  142. if (newTouchTarget == null && mFirstTouchTarget != null) {// 如果newTouchTarge为空 且 mFirstTouchTarget不为空
  143. // Did not find a child to receive the event.
  144. // Assign the pointer to the least recently added target.
  145. newTouchTarget = mFirstTouchTarget;
  146. while (newTouchTarget.next != null) {
  147. newTouchTarget = newTouchTarget.next;
  148. }
  149. newTouchTarget.pointerIdBits |= idBitsToAssign;
  150. }
  151. }
  152. }
  153. // Dispatch to touch targets.分发给touch targets
  154. if (mFirstTouchTarget == null) {
  155. // 没有触摸的对象就把它当做一个普通的View
  156. // No touch targets so treat this as an ordinary view.
  157. handled = dispatchTransformedTouchEvent(ev, canceled, null,
  158. TouchTarget.ALL_POINTER_IDS);// 第三个参数本应为child,这里是null意味着需要调用父类View的dispatchTouchEvent方法,然后调用onTouch方法
  159. } else {// 找到最终消费事件的对象
  160. // Dispatch to touch targets, excluding the new touch target if we already
  161. // dispatched to it. Cancel touch targets if necessary.
  162. TouchTarget predecessor = null;
  163. TouchTarget target = mFirstTouchTarget;
  164. while (target != null) {
  165. final TouchTarget next = target.next;
  166. if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
  167. handled = true;// 找到了
  168. } else {
  169. final boolean cancelChild = resetCancelNextUpFlag(target.child)
  170. || intercepted;
  171. if (dispatchTransformedTouchEvent(ev, cancelChild,
  172. target.child, target.pointerIdBits)) {
  173. handled = true;
  174. }
  175. if (cancelChild) {
  176. if (predecessor == null) {// 为空
  177. mFirstTouchTarget = next;// 里面没有子控件
  178. } else {
  179. predecessor.next = next;// 里面有子控件,后面需要消耗此事件
  180. }
  181. target.recycle();
  182. target = next;
  183. continue;
  184. }
  185. }
  186. predecessor = target;
  187. target = next;
  188. }
  189. }
  190. // Update list of touch targets for pointer up or cancel, if needed.
  191. if (canceled
  192. || actionMasked == MotionEvent.ACTION_UP
  193. || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
  194. resetTouchState();
  195. } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
  196. final int actionIndex = ev.getActionIndex();
  197. final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
  198. removePointersFromTouchTargets(idBitsToRemove);
  199. }
  200. }
  201. if (!handled && mInputEventConsistencyVerifier != null) {
  202. mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
  203. }
  204. return handled;
  205. }
复制代码
ViewGroup拦截情况源码分析

首先我们来看一下第34行~48行的代码,ViewGroup在如下两种情况下会判断是否要拦截当前事件:

事件类型为ACTION_DOWN或者 mFirstTouchTarget != null

即,当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget 会被赋值并指向子元素,换句话说,当ViewGroup不拦截事件并将事件交由子元素处理时mFirstTouchTarget != null。反过来,一旦事件由当前的ViewGroup拦截时,mFirstTouchTarget != null条件就不成立。那么当ACTION_MOVE和UP事件到来时,由于actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null这个条件为false,导致ViewGroup的onInterceptTouchEvent不会再被调用,并且同一序列中的其他事件都会默认交给它处理。
另外,这里有一种特殊情况,我们看36行代码,有个FLAG_DISALLOW_INTERCEPT标记为,这个标记是通过requestDisallowInterceptTouchEvent()方法来设置的,一般用在子View中。如果我们通过reqeustDisallowInterceptTouchEvent()方法设置了FLAG_DISALLOW_INTERCEPT标记位后,ViewGroup将无法拦截除了ACTION_DOWN以外的其他方法(即调用该方法并不影响ACTION_DOWN事件处理)。因为ViewGroup会在ACTION_DWON事件到来时做重置状态操作,这里从代码第22~29行可以看出。

requestDisallowInterceptTouchEvent源码解析
  1. /**
  2. * {@inheritDoc}
  3. */
  4. public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
  5. if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
  6. // We're already in this state, assume our ancestors are too
  7. return;
  8. }
  9. if (disallowIntercept) {
  10. mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
  11. } else {
  12. mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  13. }
  14. // Pass it up to our parent
  15. if (mParent != null) {
  16. mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
  17. }
  18. }
复制代码
取消、清理、重置之前的触摸状态
  1. /**
  2. * Cancels and clears all touch targets.
  3. */
  4. private void cancelAndClearTouchTargets(MotionEvent event) {
  5. if (mFirstTouchTarget != null) {// 如果保存的第一个触摸View对象不为空
  6. boolean syntheticEvent = false;
  7. if (event == null) {
  8. final long now = SystemClock.uptimeMillis();
  9. event = MotionEvent.obtain(now, now,
  10. MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
  11. event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
  12. syntheticEvent = true;
  13. }
  14. for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
  15. resetCancelNextUpFlag(target.child);
  16. dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
  17. }
  18. clearTouchTargets();
  19. if (syntheticEvent) {
  20. event.recycle();
  21. }
  22. }
  23. }
  24. /**
  25. * Resets all touch state in preparation for a new cycle.
  26. */
  27. private void resetTouchState() {
  28. clearTouchTargets();
  29. resetCancelNextUpFlag(this);
  30. mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  31. mNestedScrollAxes = SCROLL_AXIS_NONE;
  32. }
复制代码

因此我们可以得出如下结论:

1.当ViewGroup决定拦截事件后,那么点击事件将会默认交给它处理并且不再调用它的onInterceptTouchEvent方法,FLAG_DISALLOW_INTERCEPT这个标记的作用是ViewGroup不再拦截事件,前提是ViewGroup不拦截ACTION_DOWN事件处理。

2.如果事件能够传递到当前的ViewGroup,且我们要提前处理所有点击事件,应该选择dispatchTouchEvent方法,因为只有这个方法能确保每次都会被调用;而onInterceptTouchEvent()却无法保证每次事件都会被调用。

3.FLAG_DISALLOW_INTERCEPT标记位可以用于解决滑动冲突问题。

ViewGroup不拦截情况源码分析

ViewGroup不拦截事件的时候,事件会向下分发交由它的子View进行处理。先来看下代码64行(!canceled && !intercepted)这里的canceled和intercepted都为false时,条件成立,也就是说不拦截。接下来74行的条件判断:

  1. actionMasked == MotionEvent.ACTION_DOWN
  2. || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
  3. || actionMasked == MotionEvent.ACTION_HOVER_MOVE
复制代码

在该if条件内,看到第86行,如果newTouchTarget == null && childrenCount != 0,即子控件的个数不为0 且 newTouchTarget为空,在95行中遍历整个ViewGroup中的子控件,这里的集合做了个倒序排列,如果两个View交叉覆盖在一起,下面的子控件先放进集合,因为后被添加的子控件会浮在上面,通常我们会希望点击的时候最上层的那个组件先去响应事件。接着105行代码开始判断子控件是否能够接收到点击事件,主要依赖于两个条件:第一子控件是否在播动画;第二点击事件是否落在子控件的区域内。如果某个子控件满足这两个条件,那么事件就会传递给它来处理。

buildOrderedChildList方法解析
  1. /**
  2. * 实现倒序排序
  3. * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children,
  4. * sorted first by Z, then by child drawing order (if applicable). This list must be cleared
  5. * after use to avoid leaking child Views.
  6. * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated
  7. * children.
  8. */
  9. ArrayList<View> buildOrderedChildList() {
  10. final int count = mChildrenCount;
  11. if (count <= 1 || !hasChildWithZ()) return null;
  12. if (mPreSortedChildren == null) {
  13. mPreSortedChildren = new ArrayList<View>(count);
  14. } else {
  15. mPreSortedChildren.ensureCapacity(count);
  16. }
  17. final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
  18. for (int i = 0; i < mChildrenCount; i++) {
  19. // add next child (in child order) to end of list
  20. int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i;
  21. View nextChild = mChildren[childIndex];
  22. float currentZ = nextChild.getZ();
  23. // insert ahead of any Views with greater Z
  24. int insertIndex = i;
  25. while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
  26. insertIndex--;
  27. }
  28. mPreSortedChildren.add(insertIndex, nextChild);
  29. }
  30. return mPreSortedChildren;
  31. }
复制代码

接着看代码,129行通过dispatchTransformedTouchEvent()这一重要方法(后面有详细分析),判断是否有子控件,如果有子控件则执行内部的操作,并找到最终触摸的对象,通过addTouchTarget方法赋值给newTouchTarget。在dispatchTransformedTouchEvent()方法中,如果子控件的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环,详见148行代码。同样如果dispatchTouchEvent()方法返回false,ViewGroup就会把事件分发给下一个子控件(如果还有下一个子控件)。

mFirstTouchEvent的真正赋值其实是在addTouchTarget方法中完成的,mFirstTouchEvent其实是一个单链表结构,如果mFirstTouchEvent为null,那么ViewGroup就会默认拦截下来同一序列中所有的点击事件。

addTouchTarget方法解析
  1. /**
  2. * Adds a touch target for specified child to the beginning of the list.
  3. * Assumes the target child is not already present.
  4. */
  5. private TouchTarget addTouchTarget(View child, int pointerIdBits) {
  6. TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
  7. // 往链表中插入一个元素
  8. target.next = mFirstTouchTarget;// 将mFirstTouchTarget赋值给target.next
  9. mFirstTouchTarget = target;// 将target赋值给mFirstTouchTarget
  10. return target;
  11. }
复制代码

接着我们看到171行代码中,如果mFirstTouchEvent为null,也就是说要么ViewGroup中没有子控件,要么是子控件处理了点击事件,但是在dispatchTouchEvent中返回了false(子控件在onTouchEvent中返回了false),那么ViewGroup就会自己处理点击事件,需要说明的是175行代码中,第三个参数本应为child,这里是null意味着需要调用父类View的dispatchTouchEvent方法,然后调用onTouch方法。

TouchTarget 内部类源码解析
  1. /* Describes a touched view and the ids of the pointers that it has captured.
  2. * 链表实现的内部类,解决多指触控问题用来指定当前触摸的对象,多个手指触控(0~31个手指)
  3. * This code assumes that pointer ids are always in the range 0..31 such that
  4. * it can use a bitfield to track which pointer ids are present.
  5. * As it happens, the lower layers of the input dispatch pipeline also use the
  6. * same trick so the assumption should be safe here...
  7. */
  8. private static final class TouchTarget {
  9. private static final int MAX_RECYCLED = 32;
  10. private static final Object sRecycleLock = new Object[0];
  11. private static TouchTarget sRecycleBin;
  12. private static int sRecycledCount;
  13. public static final int ALL_POINTER_IDS = -1; // all ones
  14. // The touched child view.
  15. public View child;
  16. // The combined bit mask of pointer ids for all pointers captured by the target.
  17. public int pointerIdBits;
  18. // The next target in the target list.
  19. public TouchTarget next;
  20. private TouchTarget() {
  21. }
  22. public static TouchTarget obtain(View child, int pointerIdBits) {
  23. final TouchTarget target;
  24. synchronized (sRecycleLock) {
  25. if (sRecycleBin == null) {
  26. target = new TouchTarget();
  27. } else {
  28. target = sRecycleBin;
  29. sRecycleBin = target.next;
  30. sRecycledCount--;
  31. target.next = null;
  32. }
  33. }
  34. target.child = child;
  35. target.pointerIdBits = pointerIdBits;// 手指的ID
  36. return target;
  37. }
  38. public void recycle() {
  39. synchronized (sRecycleLock) {
  40. if (sRecycledCount < MAX_RECYCLED) {
  41. next = sRecycleBin;
  42. sRecycleBin = this;
  43. sRecycledCount += 1;
  44. } else {
  45. next = null;
  46. }
  47. child = null;
  48. }
  49. }
  50. }
复制代码
至关重要的dispatchTransformedTouchEvent方法解析
  1. /**
  2. * Transforms a motion event into the coordinate space of a particular child view,
  3. * filters out irrelevant pointer ids, and overrides its action if necessary.
  4. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
  5. */
  6. private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
  7. View child, int desiredPointerIdBits) {
  8. final boolean handled;
  9. // Canceling motions is a special case. We don't need to perform any transformations
  10. // or filtering. The important part is the action, not the contents.
  11. final int oldAction = event.getAction();
  12. if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
  13. event.setAction(MotionEvent.ACTION_CANCEL);
  14. if (child == null) {// 如果child为空,则调用自己的分发方法
  15. handled = super.dispatchTouchEvent(event);
  16. } else {// 否则调用child的分发方法
  17. handled = child.dispatchTouchEvent(event);
  18. }
  19. event.setAction(oldAction);
  20. return handled;
  21. }
  22. // Calculate the number of pointers to deliver.
  23. final int oldPointerIdBits = event.getPointerIdBits();
  24. final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
  25. // If for some reason we ended up in an inconsistent state where it looks like we
  26. // might produce a motion event with no pointers in it, then drop the event.
  27. if (newPointerIdBits == 0) {
  28. return false;
  29. }
  30. // If the number of pointers is the same and we don't need to perform any fancy
  31. // irreversible transformations, then we can reuse the motion event for this
  32. // dispatch as long as we are careful to revert any changes we make.
  33. // Otherwise we need to make a copy.
  34. final MotionEvent transformedEvent;
  35. if (newPointerIdBits == oldPointerIdBits) {
  36. if (child == null || child.hasIdentityMatrix()) {
  37. if (child == null) {
  38. handled = super.dispatchTouchEvent(event);
  39. } else {
  40. final float offsetX = mScrollX - child.mLeft;
  41. final float offsetY = mScrollY - child.mTop;
  42. event.offsetLocation(offsetX, offsetY);
  43. handled = child.dispatchTouchEvent(event);
  44. event.offsetLocation(-offsetX, -offsetY);
  45. }
  46. return handled;
  47. }
  48. transformedEvent = MotionEvent.obtain(event);
  49. } else {
  50. transformedEvent = event.split(newPointerIdBits);
  51. }
  52. // Perform any necessary transformations and dispatch.
  53. if (child == null) {// 如果ViewGroup中没有子控件,调用父类View的dispatchTouchEvent
  54. handled = super.dispatchTouchEvent(transformedEvent);
  55. } else {// 如果有子控件
  56. final float offsetX = mScrollX - child.mLeft;
  57. final float offsetY = mScrollY - child.mTop;
  58. transformedEvent.offsetLocation(offsetX, offsetY);
  59. if (! child.hasIdentityMatrix()) {
  60. transformedEvent.transform(child.getInverseMatrix());
  61. }
  62. // 调用子控件的dispatchTouchEvent,有两种情况,如果是子控件是View,又分成两种情况(Button返回true,TextView返回false);如果是ViewGroup则进入递归了,又回到了这段代码,最终要么没有任何消耗事件的View,要么找到消费事件的View
  63. handled = child.dispatchTouchEvent(transformedEvent);
  64. }
  65. // Done.
  66. transformedEvent.recycle();
  67. return handled;
  68. }
复制代码

该方法在dispatchTouchEvent()中被调用,用于将事件分发给子View处理。我们重点看一下60~71行代码。在dispatchTransformedTouchEvent()方法一共有三个参数,其中第三个参数View child有时为null,有时不为null。61行代码中,child==null意味着事件没有被消费,ViewGroup中没有子控件需要调用父类View的dispatchTouchEvent方法,即super.dispatchTouchEvent(event)。 接着我们关注下handled这个变量,可以发现dispatchTransformedTouchEvent()方法return handled,而handled的值其实是取决于dispatchTransformedTouchEvent()方法递归调用dispatchTouchEvent()方法的结果,也就是说在子控件中dispatchTouchEvent()方法的onTouchEvent()是否消费了Touch事件的返回值决定了dispatchTransformedTouchEvent()的返回值,从而决定mFirstTouchTarget是否为null,更进一步决定了ViewGroup是否处理Touch事件。

(二)onInterceptTouchEvent源码解析
  1. public boolean onInterceptTouchEvent(MotionEvent ev) {
  2. return false;// 默认不拦截
  3. }
复制代码

你没有看错,这方法就是简简单单的一个布尔值返回,当返回true时,对事件进行拦截,返回false则不拦截。

(三)ViewGroup点击事件分发小结

Android点击事件分发是到达顶级View后(一般是ViewGroup),会调用ViewGroup的dispatchTouchEvent方法,其中它的onInterceptTouchEvent方法如果返回true,则会对事件传递进行拦截,事件由ViewGroup处理;如果onInterceptTouchEvent方法返回false,则代表不对事件进行拦截,默认返回false,即所有的ViewGroup都是默认不拦截的。则此时子View中的dispatchTouchEvent方法将被调用,到此,事件已经由顶级View传递给了下一层的View,接下来的过程是一个递归循环的过程,和顶级View事件分发过程是一致的,直到完成整个事件分发。

这里写图片描述



回复

使用道具 举报