非常早之前写过一篇android事件分发的博客,主要写的是它是怎样分发的,具体非常多原理的东西都没有涉及到。今天就从源代码看android怎样控制它的分发机制。

鉴于手机屏幕的限制,所以android选择了分层的方式布局,这就引出了今天的主题--事件分发

当你点击一个控件或者某个空白区域时,怎样确定你点击的位置,事件又是怎样传递到这里的,相信看过上篇博客的都知道怎样传递了,以下就開始看源代码

  1. public boolean dispatchTouchEvent(MotionEvent event) {
  2. boolean result = false;
  3. if (mInputEventConsistencyVerifier != null) {
  4. mInputEventConsistencyVerifier.onTouchEvent(event, 0);
  5. }
  6. final int actionMasked = event.getActionMasked();
  7. if (actionMasked == MotionEvent.ACTION_DOWN) {
  8. // Defensive cleanup for new gesture
  9. stopNestedScroll();
  10. }
  11. if (onFilterTouchEventForSecurity(event)) {
  12. //noinspection SimplifiableIfStatement
  13. ListenerInfo li = mListenerInfo;
  14. if (li != null && li.mOnTouchListener != null
  15. && (mViewFlags & ENABLED_MASK) == ENABLED
  16. && li.mOnTouchListener.onTouch(this, event)) {
  17. result = true;
  18. }
  19. if (!result && onTouchEvent(event)) {
  20. result = true;
  21. }
  22. }
  23. if (!result && mInputEventConsistencyVerifier != null) {
  24. mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
  25. }
  26. // Clean up after nested scrolls if this is the end of a gesture;
  27. // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
  28. // of the gesture.
  29. if (actionMasked == MotionEvent.ACTION_UP ||
  30. actionMasked == MotionEvent.ACTION_CANCEL ||
  31. (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
  32. stopNestedScroll();
  33. }
  34. return result;
  35. }

从上面的代码能够看出主要代码就是第三个if了,首先检測事件的安全性,推断监听事件是否为空,是否有touch事件,推断当前控件是否为enable,回调onTouch()事件的返回值,那个down和up时停止滚动等先无论,看核心代码.

  1. ListenerInfo getListenerInfo() {
  2. if (mListenerInfo != null) {
  3. return mListenerInfo;
  4. }
  5. mListenerInfo = new ListenerInfo();
  6. return mListenerInfo;
  7. }

从以上这段代码会看出mListenerInfo一定会被赋值,当然限定于ListenerInfo有的监听事件,监听事件不为空时,监听事件就存在onTouchListener,第三个条件基本都满足,Android控件默认的情况下都是enable,除非你手动设置,第四个条件,假设回调的onTouch()事件返回的为true,dispatchTouchEvent(event)直接返回true,否则继续走onTouchEvent(event)

从上面能够看出onTouchEvent(event)就是dispatchTouchEvent(event)的小棋子,小棋子被运行的话,返回值就是dispatchTouchEvent(event)的返回值,也就验证了之前说的假设dispatchTouchEvent(event)返回true就交给onTouchEvent(event)去运行

  1. public boolean onTouchEvent(MotionEvent event) {
  2. final float x = event.getX();
  3. final float y = event.getY();
  4. final int viewFlags = mViewFlags;
  5. if ((viewFlags & ENABLED_MASK) == DISABLED) {
  6. if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
  7. setPressed(false);
  8. }
  9. // A disabled view that is clickable still consumes the touch
  10. // events, it just doesn't respond to them.
  11. return (((viewFlags & CLICKABLE) == CLICKABLE ||
  12. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
  13. }
  14. if (mTouchDelegate != null) {
  15. if (mTouchDelegate.onTouchEvent(event)) {
  16. return true;
  17. }
  18. }
  19. if (((viewFlags & CLICKABLE) == CLICKABLE ||
  20. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
  21. switch (event.getAction()) {
  22. case MotionEvent.ACTION_UP:
  23. boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
  24. if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
  25. // take focus if we don't have it already and we should in
  26. // touch mode.
  27. boolean focusTaken = false;
  28. if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
  29. focusTaken = requestFocus();
  30. }
  31. if (prepressed) {
  32. // The button is being released before we actually
  33. // showed it as pressed. Make it show the pressed
  34. // state now (before scheduling the click) to ensure
  35. // the user sees it.
  36. setPressed(true, x, y);
  37. }
  38. if (!mHasPerformedLongPress) {
  39. // This is a tap, so remove the longpress check
  40. removeLongPressCallback();
  41. // Only perform take click actions if we were in the pressed state
  42. if (!focusTaken) {
  43. // Use a Runnable and post this rather than calling
  44. // performClick directly. This lets other visual state
  45. // of the view update before click actions start.
  46. if (mPerformClick == null) {
  47. mPerformClick = new PerformClick();
  48. }
  49. if (!post(mPerformClick)) {
  50. performClick();
  51. }
  52. }
  53. }
  54. if (mUnsetPressedState == null) {
  55. mUnsetPressedState = new UnsetPressedState();
  56. }
  57. if (prepressed) {
  58. postDelayed(mUnsetPressedState,
  59. ViewConfiguration.getPressedStateDuration());
  60. } else if (!post(mUnsetPressedState)) {
  61. // If the post failed, unpress right now
  62. mUnsetPressedState.run();
  63. }
  64. removeTapCallback();
  65. }
  66. break;
  67. case MotionEvent.ACTION_DOWN:
  68. mHasPerformedLongPress = false;
  69. if (performButtonActionOnTouchDown(event)) {
  70. break;
  71. }
  72. // Walk up the hierarchy to determine if we're inside a scrolling container.
  73. boolean isInScrollingContainer = isInScrollingContainer();
  74. // For views inside a scrolling container, delay the pressed feedback for
  75. // a short period in case this is a scroll.
  76. if (isInScrollingContainer) {
  77. mPrivateFlags |= PFLAG_PREPRESSED;
  78. if (mPendingCheckForTap == null) {
  79. mPendingCheckForTap = new CheckForTap();
  80. }
  81. mPendingCheckForTap.x = event.getX();
  82. mPendingCheckForTap.y = event.getY();
  83. postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
  84. } else {
  85. // Not inside a scrolling container, so show the feedback right away
  86. setPressed(true, x, y);
  87. checkForLongClick(0);
  88. }
  89. break;
  90. case MotionEvent.ACTION_CANCEL:
  91. setPressed(false);
  92. removeTapCallback();
  93. removeLongPressCallback();
  94. break;
  95. case MotionEvent.ACTION_MOVE:
  96. drawableHotspotChanged(x, y);
  97. // Be lenient about moving outside of buttons
  98. if (!pointInView(x, y, mTouchSlop)) {
  99. // Outside button
  100. removeTapCallback();
  101. if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
  102. // Remove any future long press/tap checks
  103. removeLongPressCallback();
  104. setPressed(false);
  105. }
  106. }
  107. break;
  108. }
  109. return true;
  110. }
  111. return false;
  112. }

好长的代码,还是看核心代码,前面介绍了Android的控件默认都是enable的,所以第一个if跳过,直接看第三个if,控件是否可点击(或者是是否有长按事件)进入switch中,以下就開始Down,Move,Up

先来看ACTION_DOWN,首先推断当前点击down事件的操作,假设点击到menu时,显示菜单,返回true,程序直接跳出

  1. protected boolean performButtonActionOnTouchDown(MotionEvent event) {
  2. if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
  3. if (showContextMenu(event.getX(), event.getY(), event.getMetaState())) {
  4. return true;
  5. }
  6. }
  7. return false;
  8. }

再来看一下ACTION_UP,按不按的那一堆直接跳过,直接看核心代码 performClick(),这个是干嘛的,进去看看

  1. public boolean performClick() {
  2. final boolean result;
  3. final ListenerInfo li = mListenerInfo;
  4. if (li != null && li.mOnClickListener != null) {
  5. playSoundEffect(SoundEffectConstants.CLICK);
  6. li.mOnClickListener.onClick(this);
  7. result = true;
  8. } else {
  9. result = false;
  10. }
  11. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  12. return result;
  13. }

这里看到了熟悉的代码onClick(),仅仅要li和li.mOnClickListener不为空,onClick()就会被运行,上面说过li不会为空,li.mOnClickListener赋值例如以下:

  1. public void setOnClickListener(OnClickListener l) {
  2. if (!isClickable()) {
  3. setClickable(true);
  4. }
  5. getListenerInfo().mOnClickListener = l;
  6. }

仅仅要注冊了点击事件li.mOnClickListener也不会为空,所以仅仅要你注冊点击事件onClick()就会被运行,当然前提是运行Up事件哦!

看完这段代码是不是认为掉坑里了,Down,Move,Up,Cancel事件怎样运行都会返回true,除非这个控件不能点击,为什么会返回true呢,为了能够之后后面的action,假设程序运行完Down返回false的话后面的Move.Up都不会运行

总结一下view的事件分发结果,首先当你touch到某个点或者区域的时候,先去找这个控件的dispatchTouchEvent(event),该控件没有的话继续找父类,直到找到为止,然后開始事件分发,假设该控件重写了onTouch()事件,返回true的话,dispatchTouchEvent(event)直接返回true,返回false的话,继续运行onTouchEvent(event),假设该控件没有点击事件,像ImageView,TextView等直接返回false,假设存在点击事件,像Button,ImageButton等去运行Down,Move,Up等,返回true,dispatchTouchEvent(event)相应的返回true or false

以上就是Android中view的事件分发的内容了,以下我们继续研究viewgroup的事件分发,viewgroup继承自view,所以在事件分发上也有一定的类似性,仅仅是viewgroup更复杂一些

先来看viewgroup的dispatchTouchEvent(event)

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

喔,好长啊,还是同上,仅仅看核心代码,首先推断当前是不是down状态或者是首次touch,然后推断disallowIntercept的值,默认情况下为false,你也能够通过调用以下这种方法改变它的值

  1. public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
  2. if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
  3. // We're already in this state, assume our ancestors are too
  4. return;
  5. }
  6. if (disallowIntercept) {
  7. mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
  8. } else {
  9. mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  10. }
  11. // Pass it up to our parent
  12. if (mParent != null) {
  13. mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
  14. }
  15. }

继续上面说的,怎么突然间出现了onInterceptTouchEvent(),这种方法就是传说中的事件拦截了

  1. public boolean onInterceptTouchEvent(MotionEvent ev) {
  2. return false;
  3. }

原来这个高大上的拦截事件就两行,直接给你return false,也就说默认情况下是不拦截的,当然There are no touch targets and this action is not an initial down的情况下也是拦截的.假设当前事件被拦截,事件就不再向下分发,假设当前事件没有被拦截而且没有取消,继续走view的事件分发

最后总结一下:点击到某个点,先传递到viewgroup的

dispatchTouchEvent(event),然后传递到onInterceptTouchEvent(event),推断当前是否拦截,不拦截就继续走view的拦截方式。拦截就谁拦截谁处理。

分析的不够仔细,具体内容请看源代码。自行补脑。

android事件分发(二)的更多相关文章

  1. Android事件分发机制二:viewGroup与view对事件的处理

    前言 很高兴遇见你~ 在上一篇文章 Android事件分发机制一:事件是如何到达activity的? 中,我们讨论了触摸信息从屏幕产生到发送给具体 的view处理的整体流程,这里先来简单回顾一下: 触 ...

  2. Android事件分发机制浅谈(一)

    ---恢复内容开始--- 一.是什么 我们首先要了解什么是事件分发,通俗的讲就是,当一个触摸事件发生的时候,从一个窗口到一个视图,再到一个视图,直至被消费的过程. 二.做什么 在深入学习android ...

  3. 通俗理解Android事件分发与消费机制

    深入:Android Touch事件传递机制全面解析(从WMS到View树) 通俗理解Android事件分发与消费机制 说起Android滑动冲突,是个很常见的场景,比如SliddingMenu与Li ...

  4. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  5. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  6. 【朝花夕拾】Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/10998855.html]谢谢! 在自定义View中,经常需要处理Android事件分发的问题, ...

  7. 【朝花夕拾】Android自定义View篇之(七)Android事件分发机制(下)滑动冲突解决方案总结

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/11072989.html],谢谢! 前面两篇文章,花了很大篇幅讲解了Android的事件分发机制 ...

  8. 你真的看懂Android事件分发了吗?

    引子 Android事件分发其实是老生常谈了,但是说实话,我觉得很多人都只是懂其大概,模棱两可.本文的目的就是再次从源码层次梳理一下,重点放在ViewGroup的dispatchTouchEvent方 ...

  9. Android事件分发与责任链模式

    一.责任链模式 责任链模式是一种行为模式,为请求创建一个接收者的对象链.这样就避免,一个请求链接多个接收者的情况.进行外部解耦.类似于单向链表结构. 优点: 1. 降低耦合度.它将请求的发送者和接收者 ...

随机推荐

  1. java分段加载数据,循环和递归两种方式

    package org.jimmy.autosearch2019.test; import java.util.ArrayList; public class Test20190328 { priva ...

  2. 通过 getResources 找不到jar包中的资源和目录的解决方法

    http://my.oschina.net/sub/blog/184074 今天碰到一个怪问题: 原本跑的好好的代码,打成 jar 包就不能运行了. 问题出在,代码中有一段自动扫描 classpath ...

  3. Hibernate-02 HQL实用技术

    学习任务 Query接口的使用 HQL基本用法 动态参数绑定查询 HQL的使用 Hibernate支持三种查询方式:HQL查询.Criateria查询.Native SQL查询. HQL是Hibern ...

  4. Linux-04 Linux中Tomcat和MySQL的安装

    1.下载apache-tomcat-7.0.79-tar.tar2.解压到当前用户目录,改名为tomcat [hduser@node1 ~]$ tar -zxvf apache-tomcat-7.0. ...

  5. 关于JVM内存模型,GC策略以及类加载器的思考

    JVM内存模型 Sun在2006年将Oracle JDK开源最终形成了Open JDK项目,两者在绝大部分的代码上都保持一致.JVM的内存模型是围绕着原子性(操作有且仅有一个结果).可见性(racin ...

  6. PHP中的预定义常量、类常量和魔术常量的区别

    PHP 向它运行的任何脚本提供了大量的预定义常量.不过很多常量都是由不同的扩展库定义的,只有在加载了这些扩展库时才会出现,或者动态加载后,或者在编译时已经包括进去了. 对于一些基本的常量是这些常量在 ...

  7. (5) openssl speed(测试算法性能)和openssl rand(生成随机数)

    1.1 openssl speed 测试加密算法的性能 支持的算法有: openssl speed [md2] [mdc2] [md5] [hmac] [sha1] [rmd160] [idea-cb ...

  8. 条款36:绝不重新定义继承而来的non-virtual函数(Never redefine an inherited non-virtual function)

    NOTE: 1.绝对不要重新定义继承而来的non-virtual函数.

  9. Python中的函数(2)

    一.实参和形参        def greet_user(username): """显示简单的问候语,且显示用户名""" print(& ...

  10. 【模板】CDQ分治

    __stdcall大佬的讲解 这里贴的代码写的是点修改.区间查询的题. #include<cstdio> #include<cstring> #include<algor ...