转眼间近一年没更新博客了,工作一忙起来。非常难有时间来写博客了,因为如今也在从事Android开发相关的工作,因此以后的博文也会很多其它地专注于这一块。

这篇文章准备从源代码层面为大家带来Touch事件的传递机制。我这里分析的源代码时Android4.4的。

说到分析源代码,光看肯定是不行的,一定要亲自去跟,而且要边跟边思考,所以在下一篇中。会有一个Demo来为大家详细分析源代码的走向。

以下进入正题,先来看下Android中事件的分类:

1、键盘事件:主要是指按下虚拟键盘的某个按键、或者机身的物理按键时产生的事件。

2、鼠标事件:Android4.0之后添加了对鼠标事件的监控,如ACTION_HOVER_ENTER。

3、触摸屏事件:凡是触摸屏幕而产生的事件都是触摸屏事件。触摸屏事件包含非常多,比方单点触控、多点触控)、轨迹球事件等。

我们这里主要解说单点触控事件,也就是Touch事件的传递,首先看下Touch事件的完整传递过程:

1、首先须要明确,Android中,Touch事件的分发分服务端和应用端。在Server端由WindowManagerService(WMS,窗体管理服务。不懂的自行脑补)负责採集和分发的。在client端则是由ViewRootImpl(内部有个mView变量指向View树的根 ,负责控制View树的UI绘制和事件消息的分发)负责分发的。

 2、WMS在启动之后,经过逐层调用。会在native层启动两个线程:InputReaderThread和InputDispatchThread,前者用来读取输入事件,

后者用来分发输入事件,输入事件经过nativie层的层层传递,终于会传递到java层的ViewRootImpl中。调用

ViewPostImeInputStage(ViewRootImpl的内部类)中的各个方法来分发不同的事件,而Touch事件是在processPointerEvent方法进行分发的(这部分代码非常单,可自行查看)。

3、processPointerEvent方法中调用mView.dispatchPointerEvent(event)方法。这里的mView就是在创建窗体后通过调用root.setView传进

来的DecorView,而dispatchPointerEvent方法会对event进行推断,假设是Touch事件的话。就调用dispatchTouchEvent将该事件分发DecorView,这样。Touch事件就传递到了View树中了。

Touch事件从WMS到ViewRootImpl的传递

以下这张图(不是自己画的。网上找的,Android4.4中,InputManager变成了InputManagerService,ViewRoot变成了ViewRootImpl)展示了Touch事件

从WMS(sever端)传递到ViewRootImpl(client端)的流程。

      这里须要特别注意的是。Touch事件从server端传递到client端採用的IPC方式并非Binder。而是共享内存和管道,至于为什么不採用Binder,应该是共享内存的效率更高,而管道(注意。两个管道。分别负责不同方向的读和写)仅仅负责通知是否有事件发生,传递的仅仅是一个非常easy的字符串。因此并不会太多地影响到IPC的效率。

      上图中,仅仅有WMS、ViewRootImpl、InputManagerService、InputQueue是在FrameWork层实现的。其它部分都是在native层实现的,native 层的代码没有细看,參考了些网上的一些资料和公司内部的一些分享,把整个流程串通了。这部分代码,假设时间充足,能够深入研究下。

      在sever端中。InputReader和InputDispatcher是native 层的两个线程,前者不断地从EventHub中读取事件(包含全部的事件。对不同的事件会做推断处理)。后者则不断地分发InputReader读取到的事件。而实际的分发操作时在InputPublish中进行的,它里面保存的有一个指向server端InputChannel端的指针。和一个指向ShareMemory(共享内存)的指针。当有事件要分发时,它会将事件写入到ShareMemory中,而且传递一个特定的字符串给InputChannel。由inutChannel将该字符串写入到管道中。在client端,一旦InputChannel从管道中读取到有事件分发过来,便会通知InPutConsumer从ShareMemory中读取详细的事件。并传递到framework层的InputQueue中。相同。一旦事件消费完毕,client端会通过管道告诉server端,事件已经消费完毕,流程与上面的似。

大致的流程就是这样。

另外。顺便说下这里的两个InputChannel,这两个InputChannel是在native 层的。他们在framework层各自有一个相应的InputChannel类,对于这两个framework层的InputChannel。client端的是在ViewRootImpl中的setView中new出来的,可是并未做不论什么初始化操作(真正的初始化操作是跟server端的一起在WMS中运行的)。也就是构造方法里面为空。

  1. // Schedule the first layout -before- adding to the window
  2. // manager, to make sure we do the relayout before receiving
  3. // any other events from the system.
  4. requestLayout();
  5. if ((mWindowAttributes.inputFeatures
  6. & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
  7. mInputChannel = new InputChannel();
  8. }

server端的InputChannel尽管是在server端创建的,但其创建过程是在client端发起的。ViewRootImpl中有server端的Session的代理,相同是setview方法中。通过Session代理运行server端Session的addToDisplay方法。该方法接受了client端的InputChannel方法

  1. mOrigWindowType = mWindowAttributes.type;
  2. mAttachInfo.mRecomputeGlobalAttributes = true;
  3. collectViewAttributes();
  4. res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
  5. getHostVisibility(), mDisplay.getDisplayId(),
  6. mAttachInfo.mContentInsets, mInputChannel);

addToDisplay方法在Session类中,它会调用WindowManagerService的addWindow方法。而两个InputChannel的初始化操作都是在这里面的这这段代码中进行的。

  1. if (outInputChannel != null && (attrs.inputFeatures
  2. & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
  3. String name = win.makeInputChannelName();
  4. InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
  5. win.setInputChannel(inputChannels[0]);
  6. inputChannels[1].transferTo(outInputChannel);
  7.  
  8. mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
  9. }

这里的InputChannel.openInputChannelPair方法会在native层创建两个InputChannel,也就是我们上面看到的那两个,并返回相应的framework层InputChannel的两个对象,保存在iputChannels数组中,当中一个保留字server端,一个通过transferTo方法返回到client 端。

这里的InputChannel.openInputChannelPair方法和transferTo方法中都是直接调用来了native方法,这里不再贴代码。

       说了这么多。事实上就是一个Binder机制,关于Binder机制。大家自行在搜索吧,入门的资料还是挺多的。

这里我依据源代码画了两份ViewRootImpl和WMS之间通过Binder 机制进行IPC的序列图,有兴趣的能够自行研究下代码,仅仅要能搞清楚Binder机制。这部分代码还就不难懂。

ViewRootImpl到WMS的连接,通过WMS提供给ViewRootImpl的IWinowSession成员,也就是Session在本地的代理来完毕:

  1.  

   

     WMS到 ViewRootImpl的连接,通过ViewRootImpl提供给WMS的Iwindow(ViewRootImpl的内部类)来完毕:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

Touch事件在View树中的分发

当Touch事件传递到了ViewRootImpl中后,就会在View树中进行分发,要了解Touch事件在View树中的分发。首先须要了解View树的创建,这部分又可以写成一篇单独的博文了。详细的过程这里不再详说,有兴趣的能够自己研究下。

View树创建完毕后的结构是这种(图片源自网络):

View树的根View永远是DecorView,它继承自FrameLyout。其内部会有一个LinearLayout,依据Window Feather的的不同,LinearLayout内部的布局也不同,当中每种不同布局的xml(系统资源内部的xml布局)内都有一个id为content的FrameLayout。这就是我们在自己的布局所attach的父容器。

Touch事件的传递自然是先从ViewRootImpl传递到DecorView中。这个前面的第三点也说到了,因此我们这里就从DecorView入手,開始分析Touch事件的分发。

在開始分析之前,先大致梳理下Touch事件传递可能涉及到的一些基础:

1、普通情况下。每个Touch事件,总是以ACTION_DOWN事件開始。中间穿插着一些ACTION_MOVE事件(取决于是否有手势的移动),然后以ACTION_UP事件结束。中间还会会有onTouch、Click、LongClick等事件。

2、事件分发过程中,包含对MotionEvent事件的三种处理操作:
分发操作:dispatchTouchEvent方法,后面两个方法都是在该方法中被调用的。
拦截操作:onInterceptTouchEvent方法(ViewGroup)
消费操作:onTouchEvent方法和OnTouchListener的onTouch方法,当中onTouch的优先级高于onTouchEvent,若onTouch返回true。那么就不会调用onTouchEvent方法。

3、dispatchTouchEvent分发Touch事件是自顶向下,而onTouchEvent消费事件时自底向上。onTouchEvent和onIntercepteTouchEvent都是在dispatchTouchEvent
中被调用的。

以下,正式进入对Touch事件在View树中分发的分析:

首先来看DecorView(PhoneWindow的内部类)中dispatchTouchEvent方法:

  1. @Override
  2. public boolean dispatchTouchEvent(MotionEvent ev) {
  3. final Callback cb = getCallback();
  4. return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
  5. : super.dispatchTouchEvent(ev);
  6. }

这里的cb就是当前的Activity,Activity实现了Window.Callback接口,同一时候在Activity的attach方法中。创建PhoneWindow后。调用了
mWindow.setCallback(this)将PhoneWindow中的callback设置为当前的的Activity,因此这里cb.dispatchTouchEvent就是Activity的
dispatchTouchEvent方法,假设前面三个条件同一时候成立(通常是都成立的),则调用Activity的dispatchTouchEvent方法进行事件的分发。
否则。直接调用super.dispatchTouchEvent方法,也即是FrameLayout的dispatchTouchEvent方法,事实上即使调用了Activity的
dispatchTouchEvent方法。终于也是会调用到super.dispatchTouchEvent。我们能够继续往下看Activity的dispatchTouchEvent方法:

  1.  
  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2. if (ev.getAction() == MotionEvent.ACTION_DOWN) {
  3. onUserInteraction();
  4. }
  5. if (getWindow().superDispatchTouchEvent(ev)) {
  6. return true;
  7. }
  8. return onTouchEvent(ev);
  9. }

前面if分支不用管,这里会调用PhoneWindow的superDispatchTouchEvent方法。进去看看:

  1. @Override
  2. public boolean superDispatchTouchEvent(MotionEvent event) {
  3. return mDecor.superDispatchTouchEvent(event);
  4. }

调用了DecorView的superDispatchTouchEvent方法,再进去看看:

  1. public boolean superDispatchTouchEvent(MotionEvent event) {
  2. return super.dispatchTouchEvent(event);
  3. }

终于还是调用来DecorView的super.dispatchTouchEvent。也就是说。不管如何。DecorView的dispatchTouchEvent终于都会调用到自己父亲FrameLayout的dispatchTouchEvent方法。而我们在FrameLayout中找不到dispatchTouchEvent方法。所以,会去运行ViewGroup的
dispatchTouchEvent方法。

假设该dispatchTouchEvent返回true,说明后面有view消费掉了该事件,那就返回true,不会再去运行自身的onTouchEvent方法,否则,说明没有view消费掉该事件,会一路回传到Activity中,然后调用自己的onTouchEvent方法。该方法的实现比較简单。例如以下:

  1. public boolean onTouchEvent(MotionEvent event) {
  2. if (mWindow.shouldCloseOnTouch(this, event)) {
  3. finish();
  4. return true;
  5. }
  6.  
  7. return false;
  8. }

首先调用mWindow.shouldCloseOnTouch方法来推断是否须要关闭窗体。假设是,则finish掉该Activity,并返回true,否则。返回false,普通情况下,是返回false的,那什么时候回返回true呢?我们来看下Window类中的shouldCloseOnTouch方法:

  1. public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
  2. if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
  3. && isOutOfBounds(context, event) && peekDecorView() != null) {
  4. return true;
  5. }
  6. return false;
  7. }

这里的大致意思是,假设设置了mCloseOnTouchOutside属性为true(相应xml中的android:windowCloseOnTouchOutside属性),且当前事件为down事件。且down事件发生在该Activity范围之外,而且DecorView不为null,就返回true。非常明显。dialog形的Activity可能会发生这种情况。

以下须要重点来看下ViewGroup中的dispatchTouchEvent方法了:

  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2. //调试用的
  3. if (mInputEventConsistencyVerifier != null) {
  4. mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
  5. }
  6.  
  7. //handled为返回值,表示是否有view消费了该事件。
  8. boolean handled = false;
  9. //是否要过滤掉该Touch事件。大致是这个意思
  10. if (onFilterTouchEventForSecurity(ev)) {
  11. final int action = ev.getAction();
  12. final int actionMasked = action & MotionEvent.ACTION_MASK;
  13.  
  14. if (actionMasked == MotionEvent.ACTION_DOWN) {
  15. //因为down事件代表一个系列事件的開始,因此假设是down事件,
  16. //1、就清空掉曾经消费事件的目标view,这里主要指清空掉mFirstTouchTarget链表(保存接受Touch事件的单链表,这点在后面的代码中会看到)。并将mFirstTouchTarget置为null。
  17. //2、重置触摸状态,重置了disallowIntercept相应的标志位。该变量的值决定了onInterceptTouchEvent方法是否有效,这点后面我们会看到;还有就是重置来View的mPrivateFlags标志位,这个没去了解详细是干嘛用的。
  18.  
  19. 一般在发生app的切换。或者ANR等情况时。代码会走到这里,这一点源代码的凝视里也有。
  20.  
  21. cancelAndClearTouchTargets(ev);
  22. resetTouchState();
  23. }
  24.  
  25. // 标记是否要拦截该Touch事件,true表示拦截。false表示不拦截
  26. final boolean intercepted;
  27. //假设当前事件为down事件。或者可接受Touch事件的链表不为空。就运行if语句里的逻辑。这里注意,
  28. //1、因为down事件是一个完整事件序列的的起点,因此当发生down事件时。逻辑走到这里。还没有找到消费down事件的view,因此mFirstTouchTarget为null,
  29. //2、而对后面的move和up事件,假设前面的down事件被某个view消费掉了,则mFirstTouchTarget不为null。
  30.  
  31. 上面两种情况都会使代码进入if分支中来。
  32. if (actionMasked == MotionEvent.ACTION_DOWN
  33. || mFirstTouchTarget != null) {
  34. //是否不同意拦截。默觉得false,也就是同意该方法能够通过 requestDisallowInterceptTouchEvent方法来设置
  35. final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  36. // 假设同意拦截,则onInterceptTouchEvent有效,依据我们覆写的该方法的返回值来推断是否拦截,否则。onInterceptTouchEvent无效,不正确该事件进行拦截。
  37. if (!disallowIntercept) {
  38. intercepted = onInterceptTouchEvent(ev);
  39. ev.setAction(action); // restore action in case it was changed
  40. } else {
  41. intercepted = false;
  42. }
  43. } else {
  44. // 假设当前事件不是down事件。且之前在分发down事件的时候没有找到消费down事件的目标view,也即mFirstTouchTarget为null,则直接拦截该事件。
  45. intercepted = true;
  46. }
  47.  
  48. // 检查当前事件是否被取消
  49. final boolean canceled = resetCancelNextUpFlag(this)
  50. || actionMasked == MotionEvent.ACTION_CANCEL;
  51.  
  52. // Update list of touch targets for pointer down, if needed.
  53. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
  54. //保存消费事件的目标View所相应的 TouchTarget对象
  55. TouchTarget newTouchTarget = null;
  56. //事件是否已经分发到了目标View中。
  57.  
  58. boolean alreadyDispatchedToNewTouchTarget = false;
  59. // 假设没有被取消。而且没有被拦截,就分发该事件,注意仅仅有down事件才会走到这里去分发。对于move和up事件。则会跳过这里。直接从 mFirstTouchTarget链表中找到之前消耗down事件的目标View,直接将move和up事件非法给它,后面的代码中我们会分析到。
  60.  
  61. if (!canceled && !intercepted) {
  62. //仅仅有down事件会走到这里
  63. if (actionMasked == MotionEvent.ACTION_DOWN
  64. || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
  65. || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
  66. //Touch事件的index,对于单点触控。一直为0,这里不用深究
  67. final int actionIndex = ev.getActionIndex(); // always 0 for down
  68. final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
  69. : TouchTarget.ALL_POINTER_IDS;
  70.  
  71. // Clean up earlier touch targets for this pointer id in case they
  72. // have become out of sync.
  73. removePointersFromTouchTargets(idBitsToAssign);
  74.  
  75. //该ViewGroup中子View的个数
  76. final int childrenCount = mChildrenCount;
  77. if (newTouchTarget == null && childrenCount != 0) {
  78. //当前事件发生的位置
  79. final float x = ev.getX(actionIndex);
  80. final float y = ev.getY(actionIndex);
  81. //保存该ViewGroup中子View
  82. final View[] children = mChildren;
  83.  
  84. final boolean customOrder = isChildrenDrawingOrderEnabled();
  85. //遍历子View,找到能消费该down事件的子View,对于类型为ViewGroup的子View,在分发的时候。会递归调用到它的dispatchTouchEvent方法继续进行分发。
  86. for (int i = childrenCount - 1; i >= 0; i--) {
  87. final int childIndex = customOrder ?
  88. getChildDrawingOrder(childrenCount, i) : i;
  89. final View child = children[childIndex];
  90. //假设当前子View能够消费该down事件。而且该down事件发生的位置在当前子View的范围内,则继续运行。将down事件分发给它,否则,continue推断下一个子View可否接受该down事件。
  91. if (!canViewReceivePointerEvents(child)
  92. || !isTransformedTouchPointInView(x, y, child, null)) {
  93. continue;
  94. }
  95. //推断该能接受该down事件的child是否已经在mFirstTouchTarget链表中。假设在的话,说明child已经消费掉了该down事件,直接跳出循环。我在写demo跟代码时,没有一次走到这里的,临时不是非常清楚,如何的场景下,代码会运行到这里的break。
  96. newTouchTarget = getTouchTarget(child);
  97. if (newTouchTarget != null) {
  98. newTouchTarget.pointerIdBits |= idBitsToAssign;
  99. break;
  100. }
  101.  
  102. resetCancelNextUpFlag(child);
  103. //假设该child还没有消费掉该down事件,就直接调用dispatchTransformedTouchEvent方法将该down事件传递给该child,该方法里面会调用到child的dispatchTouchEvent方法,假设该方法返回true,则说明child消费掉了该down事件,那么就运行if语句里的逻辑,将child添加到mFirstTouchTarget链表的表头,而且将该表头赋值给newTouchTarget(參见addTouchTarget方法),同一时候 alreadyDispatchedToNewTouchTarget置为true。说明有子view消费掉了该down事件。
  104. if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
  105. // Child wants to receive touch within its bounds.
  106. mLastTouchDownTime = ev.getDownTime();
  107. mLastTouchDownIndex = childIndex;
  108. mLastTouchDownX = ev.getX();
  109. mLastTouchDownY = ev.getY();
  110. newTouchTarget = addTouchTarget(child, idBitsToAssign);
  111. alreadyDispatchedToNewTouchTarget = true;
  112. break;
  113. }
  114. }
  115. }
  116. //假设newTouchTarget为null。而且 mFirstTouchTarget不为null,也即没找到子View来消耗该事件,可是保存Touch事件的链表不为空,则把newTouchTarget赋值为最早加进(Least Recently added)mFirstTouchTarget链表的target。临时没全然搞明确这里的详细意思,跟代码都没有走到这里。
  117. if (newTouchTarget == null && mFirstTouchTarget != null) {
  118. // Did not find a child to receive the event.
  119. // Assign the pointer to the least recently added target.
  120. newTouchTarget = mFirstTouchTarget;
  121. while (newTouchTarget.next != null) {
  122. newTouchTarget = newTouchTarget.next;
  123. }
  124. newTouchTarget.pointerIdBits |= idBitsToAssign;
  125. }
  126. }
  127. }
  128.  
  129. //后面在处理MOVE和UP事件时。会直接依据上次的DOWN是否被消费掉来直接进行相应的处理。
  130.  
  131. if (mFirstTouchTarget == null) {
  132. // 假设没有子view接受该事件,则直接把当前的ViewGroup当作普通的View看待,把事件传递给自己(详见dispatchTransformedTouchEvent方法,注意第三个參数传递的是null)。
  133.  
  134. handled = dispatchTransformedTouchEvent(ev, canceled, null,
  135. TouchTarget.ALL_POINTER_IDS);
  136. } else {
  137. // 假设之前的DOWN事件被子view消费掉了。就会直接找到该子View相应的Target,将MOVE或UP事件传递给它们。
  138. TouchTarget predecessor = null;
  139. TouchTarget target = mFirstTouchTarget;
  140. while (target != null) {
  141. final TouchTarget next = target.next;
  142. if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { //假设该事件已经被消费掉了,则不再进行分发(该分支主要针对DOWN事件)
  143. handled = true;
  144. } else {
  145. //否则。就直接将DOWN或UP事件分发给目标Target(之前消费DOWN事件的view相应的target,注意dispatchTransformedTouchEvent的第三个參数为target.child),这里要注意的是,假设intercepted为true,也就是MOVE或UP事件被拦截了,则cancelChild为true,则会分发一次CANCLE事件(注意dispatchTransformedTouchEvent的第二个參数)。
  146. final boolean cancelChild = resetCancelNextUpFlag(target.child)
  147. || intercepted;
  148. if (dispatchTransformedTouchEvent(ev, cancelChild,
  149. target.child, target.pointerIdBits)) {
  150. handled = true;
  151. }
  152. if (cancelChild) {
  153. if (predecessor == null) {
  154. mFirstTouchTarget = next;
  155. } else {
  156. predecessor.next = next;
  157. }
  158. target.recycle();
  159. target = next;
  160. continue;
  161. }
  162. }
  163. predecessor = target;
  164. target = next;
  165. }
  166. }
  167.  
  168. // 假设当前事件是CANCLE或UP。会调用resetTouchState方法。清空Touch状态,这里会清空mFirstTouchTarget链表,并将mFirstTouchTarget置为null
  169. if (canceled
  170. || actionMasked == MotionEvent.ACTION_UP
  171. || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
  172. resetTouchState();
  173. } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
  174. final int actionIndex = ev.getActionIndex();
  175. final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
  176. removePointersFromTouchTargets(idBitsToRemove);
  177. }
  178. }
  179.  
  180. if (!handled && mInputEventConsistencyVerifier != null) {
  181. mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
  182. }
  183. return handled;
  184. }

另外,画了张ViewGroup中dispatchTouchEvent方法代码运行的流程图。能够有助于大家对代码总体逻辑的把握(Windows上viso中画的图,传到mac上就变成这样了,又一次保存成图片,清楚度太低,直接在PPT里面截出来了。凑合着看吧。没太大影响)。

关于上面提到的dispatchTransformedTouchEvent方法,这里就不多分析了,感兴趣能够自己分析下,另外,ViewGroup中没有复写onTouchEvent方法。

以下重点看下View中的dispatchTouchEvent方法。

  1. public boolean dispatchTouchEvent(MotionEvent event) {
  2. if (mInputEventConsistencyVerifier != null) {
  3. mInputEventConsistencyVerifier.onTouchEvent(event, 0);
  4. }
  5.  
  6. if (onFilterTouchEventForSecurity(event)) {
  7. //noinspection SimplifiableIfStatement
  8. ListenerInfo li = mListenerInfo;
  9. if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
  10. && li.mOnTouchListener.onTouch(this, event)) {
  11. return true;
  12. }
  13.  
  14. if (onTouchEvent(event)) {
  15. return true;
  16. }
  17. }
  18.  
  19. if (mInputEventConsistencyVerifier != null) {
  20. mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
  21. }
  22. return false;
  23. }

非常明显。会先推断该View有没有绑定OnTouchListener监听器,假设绑定了,而且复写了当中的onTouch方法。假设onTouch方法返回了true,那么Touch事件就被消费掉了,后面的onTouchEvent方法就不会得到运行,而假设没有被消费掉。才会运行到onTouchEvent方法。依据其返回值来判定Touch时间是否被消费掉。

这里重点关注消费Touch事件的先后顺序:onTouch先于onTouchEvent。


以下就关键来看下View中的onTouchEvent方法了。

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

这里事实上没太多要说的,重点关注:

1、onClick和onLongClick运行的时机:onClick时在UP事件中运行的,onLongClick实在Down事件中运行的,仅仅是假设在Down事件中已经运行了onLongClick的话,则mHasPerformedLongPress变量会被置为true,这样在UP事件中。就会把onClick的回调remove掉。就不会再运行onClick了。


2、仅仅要该View是clickable的,就一定会消费掉Touch事件,仅仅是,假设该View是Disable的话,尽管消费掉了Touch事件。可是不做不论什么处理。

另外。有一点大致说下:
源代码的前面部分有一个mTouchDelegate变量(默觉得null),假设它不为null的话。会将Touch事件分发给它。

详细的意思是这种。假设有两个视图v1和touchDelegate1。它们的布局相互之间不重叠。假设设置了v1.setTouchDelegate(touchDelegate1)的话,v1的触摸事件就会分发给touchDelegate1中的view(TouchDelegate中有一个view变量)。

 

为了便于总体上对源代码流程的把握,这里相同画了一个流程图

最后,关于整个Touch事件在View树中的传递流程,相同画了张流程图,看起来会更直观。有助于对总体流程的把控:

以上流程图中有些地方文字有错位。应该不影响对流程的总体理解和把握。

事实上相对来说,事件的分发处理属于Android中比較基础的知识点。但想把整个流程完整地串通,还是要花些时间的。这篇文章在10月份的时候就想写了,可是工作后,写博客的时间越来越少。人也变得越来越懒了。

。。

整篇文章断断续续坚持着写下来还是挺费劲的。

好了,不多说了,下篇文章,将通过一个Demo,结合8种不同的情况,对Touch事件在View树中传递时源代码的运行情况做一个详细的分析。

          知乎上能够看到一个不一样的我,欢迎关注:兰亭风雨的知乎首页

         一直想做个公众号。可是考虑到在手机端看代码的用户体验确实太差。而我又比較喜欢正能量的东东。最后倒腾了一个鸡汤号,每日一篇正能量好文,同一时候各种互联网信息爆料。尽在当中。

。。

欢迎扫码关注。


Android Touch事件传递机制全面解析(从WMS到View树)的更多相关文章

  1. Android Touch事件传递机制 二:单纯的(伪生命周期)

    转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...

  2. Android Touch事件传递机制 一: OnTouch,OnItemClick(监听器),dispatchTouchEvent(伪生命周期)

      ViewGroup View  Activity dispatchTouchEvent 有 有 有 onInterceptTouchEvent 有 无 无 onTouchEvent 有 有 有 例 ...

  3. Android touch 事件传递机制

    前言: (1)在自定义view的时候经常会遇到事件拦截处理,比如在侧滑菜单的时候,我们希望在侧滑菜单里面有listview控件,但是我们希望既能左右滑动又能上下滑动,这个时候就需要对触摸的touch事 ...

  4. Android Touch事件传递机制通俗讲解

    在讲正题之前我们讲一段有关任务传递的小故事,抛砖迎玉下: 话说一家软件公司,来一个任务,分派给了开发经理去完成: 开发经理拿到,看了一下,感觉好简单,于是 开发经理:分派给了开发组长 开发组长:分派给 ...

  5. Android Touch事件传递机制 二:单纯的(伪生命周期) 这个清楚一点

    转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...

  6. Android Touch事件传递机制引发的血案

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38942135 关于Android Touch事件传递机制我之前也写过两篇文章,自觉得对Tou ...

  7. Android Touch事件传递机制详解 下

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...

  8. Android Touch事件传递机制具体解释 下

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...

  9. (转)Android Touch事件传递机制

    -----来源:http://www.trinea.cn/android/touch-event-delivery-mechanism/ 介绍Android Touch事件的传递机制. 不少朋友私信问 ...

随机推荐

  1. Java泛型(一):入门、原理、使用

    远在 JDK 1.4 版本的时候,那时候是没有泛型的概念的.当时 Java 程序员们写集合类的代码都是类似于下面这样: List list = new ArrayList(); list.add(&q ...

  2. 漫说好管理vs.坏管理

    天地会珠海分舵注:本文英文版来自Medium今日热点头条.漫画简单明了,全文差点儿没有多余的语言去装饰.两天内获得两千三百多个推荐,且读者的反馈也相当的热烈.中文版由天地会珠海分舵编译后分享给大家. ...

  3. 6.设置ListView的Item的高度无效

    问题: 设置ListView的Item的高度无效. 解决方式: 设置ListView的Item的minHeight属性.

  4. clipper库使用的一些心得

    clipper sourceforge官网:http://sourceforge.net/projects/polyclipping/ 1. 版本号差异 之前project里面使用4.8.6,近期升级 ...

  5. 视差滚动demo (pc)

    根据设计图设定每屏的高度,js会自动缩放到全屏尺寸,效果要大尺寸才能看的出来 demo :http://runjs.cn/detail/uvizsekd <!DOCTYPE html> & ...

  6. MySQL List分区(三)

    具体介绍请看   MySQL分区一 样例:该样例为本人个人学习总结分享

  7. Java 零基础跑起第一个程序

    Java 零基础跑起第一个程序 一 概述 1  java代码编译 编译后才干在计算机中执行.编译就是把人能看懂的代码转换成机器能看懂的形式 2 java的长处 一次编译.到处执行.由于java代码是在 ...

  8. HMM XSS检测

    HMM XSS检测 转自:http://www.freebuf.com/articles/web/133909.html 前言 上篇我们介绍了HMM的基本原理以及常见的基于参数的异常检测实现,这次我们 ...

  9. UINavi中push控制器的时候隐藏TabBar

    当一个UITabbarController管理多个UINavigationController的时候,我们又从这每一个UINavigationController中push一个ViewControll ...

  10. 三种启动SQLSERVER服务的方法(启动sqlserver服务器,先要启动sqlserver服务)

    1.后台启动 计算机-管理-服务和应用程序 2.SQL SERVER配置管理器 3.在运行窗口中使用命令进行启动: