view的事件传递机制

来源:互联网 发布:银行管理系统java 编辑:程序博客网 时间:2024/05/19 17:48

下面进入正题,先来看下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,应该是共享内存的效率更高,而管道(注意,两个管道,分别负责不同方向的读和写)只负责通知是否有事件发生,传递的只是一个很简单的字符串,因此并不会太多地影响到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中执行的),也就是构造方法里面为空。

[java] view plain copy
  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方法

[java] view plain copy
  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的初始化操作都是在这里面的这这段代码中进行的。

[java] view plain copy
  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在本地的代理来完成:


   

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



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方法:

[java] view plain copy
  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方法:

[java] view plain copy
  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方法,进去看看:

[java] view plain copy
  1. @Override  
  2. public boolean superDispatchTouchEvent(MotionEvent event) {  
  3.     return mDecor.superDispatchTouchEvent(event);  
  4. }  

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

[java] view plain copy
  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方法,该方法的实现比较简单,如下:

[java] view plain copy
  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方法:

[java] view plain copy
  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方法了:

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


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



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


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

[java] view plain copy
  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方法了。

[java] view plain copy
  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月份的时候就想写了,但是工作后,写博客的时间越来越少,人也变得越来越懒了。。。整篇文章断断续续坚持着写下来还是挺费劲的。

原创粉丝点击