android touch事件学习与理解(一)(android内核剖析)

   A.触摸消息整体派发过程

       1.进行物理像素到逻辑像素的转换,例如 800*480像素分辨率的屏幕,操作系统却定义成480*320像素,触摸消息本对应物理屏幕,所以需要转换到系统逻辑坐标。

       2.如果是down消息,则调用ensureTouchMode(true)进入触摸模式。

       3.将屏幕坐标转换到视图坐标。触摸消息本身的坐标位置是相对于屏幕左上脚,对于800*480像素的屏幕,视图可以认为是没有边界的,它内部处理消息时所需要的坐标是相对于视图本身的。视图坐标Y=event.getY + 视图.mCurScrollY。

       4.调用dispatchTouchEvent()将消息派发给根视图,该函数负责将消息派发到整个View树。对于Activity包含的窗口,根视图就是Phone Window中的DecorView,对于非应用窗口,根视图就是一个普通的ViewGroup。


   B.根视图内部消息派发过程

       1.Decorview中,先判断Activity对象是否存在,存在则调用dispatchTouchEvent(),该函数处理如下:                a.调用所包含的Window对象的superDispatchTouchEvent(),如果window类没有消耗该消息,则调用onTouchEvent()。

           b.window对象的superDispatchTouchEvent()最终调用了ViewGroup的dispatchTouchEvent().

       2.如果Activity对象不存在,则直接调用ViewGroup的dispatchTouchEvent();


   C.ViewGroup内部消息派发流程

       递归方式先把消息派发给View树中最后一个子视图,如果子视图没有消耗该消息,才递归派发给其  父视图。

       1.将布局坐标(父View中的layout大小,即可见大小)转换为视图坐标(自己的真实大小)。视图坐标 = event.get布局坐标(即X,Y,就是)+视图.mScroll值(X,Y),需要转换的原因是因为子视图的位置都是相对于该ViewGruop的视图坐标的。

       2.处理DOWN消息,作用是判断该视图坐标落到了哪个子视图中。

           a.判断 Viewgroup本身是否被禁止获取Touch消息,如果没有禁止,且回调函数onInterceptTouchEvent()返回false,则继续递归交给子视图处理(当然要先找到子视图),如果子视图消耗了该DOWN消息,则直接返回true。

           b.寻找子视图。调用child.getHitRect(frame)函数,获取子视图在该父视图中的布局坐标,即viewgroup分配给该child的位置是什么,该位置对child来讲是布局坐标,但对于该ViewGroup来说却是视图坐标,得到frame后,调用frame.contain()方法判断该消息位置是否被包含到了该child中,如果包含且该child也是一个ViewGroup,则将坐标转换到child的坐标系中,并递归调用该child的dispatchTouchEvent().

           c.如果child是一个View,则意味着递归调用的结束。

       3.如果是UP或者CANCEL消息,则清除mGroupFlags中的FLAG_DISALLOW_INTERCEPT标识,表示重新允许ViewGroup拦截消息。

       4.判断target变量是否为空,空表示子View都没有处理该消息,所以该ViewGroup本身需要处理该消息。在第2步中,如果匹配到某个child,并且该child消耗了消息后,会将child值赋给父视图中的mMotionTarget变量。该child会调用super.dispatchTouchEvent(),即View类中的该函数,View类中的该函数内部仅仅是回调onTouchEvent(),在调用前,会先判断mPrivateFlags中是否包含CANCEL_NEXT_UP_EVENT标识,如果存在,则将消息的action类型改为ACTION_CANCEL.

       5.处理target存在,并且disallowIntercept为false,即允许截获,只有该viewGroup的子view调用父视图的requestDisallowInterceptTouchEvent(),才能禁止父视图再次拦截消息,但每次UP或CANCEL之后,该ViewGroup又会重新截获消息。如果允许,并且onInterceptTouchEvent()消耗了该消息,则在代码中将消息的action类型改为CANCEL,即“取消”,然后调用target.dispatchTouchEvent(),使得目标视图内能取消之前可能存在的消息跟踪,比如监测长按,特定手势等,onInterceptTouchEvent()执行完后,返回true;

       6.大多数情况下,target都会存在,并且ViewGroup本身不允许截获消息,或允许截获但没又消耗消息,于是调用target.dispatchTouchEvent()把该消息继续交给目标视图处理。在dispatchxxx调用之前,先检查target中是否声明过要取消随后的消息,,即mPrivateFlags中包含CANCEL_NEXT_UP_EVENT,如果是,则把消息action值修改为CANCEL,置空mMotionTarget变量,因为target不想处理接下来的消息了,那么就可以认为没有target了。

   以上就是ViewGroup内部的递归和派发。ViewGroup.dispatchTouchEvent()内部,如果onIntercept返回false(没有拦截),就递归给子view去处理,如果子View也没有处理,则会调用view.onTouchEvent().


   D.View内部消息监测:

           View内部已经实现了三种监测:pre-pressed,pressed,long-pressed.

           pre-pressed的时间段t0是从ACTION_DOWN开始到ViewConfigration.getTapTimeout(),

           pressed的时间段从t0开始到t1:ViewConfigration.getLongPressTimeOut().

           long-pressed时间段从t1开始。

           实现的基本原理是利用handler发送一个异步消息,ACTION_DOWN消息发生时,设置flag=pre-pressed。handler发送一个延迟为t0的消息mPendingForTapCheck。该消息内部设置flag=prepressed。如果在t1后,UP消息还没发生,就默认调用perFormLongClick().如果longclick消耗了该消息,就将mHasPerformedLongPress设为true。

           以上监测过程在View类的onTouchEvent()实现。


   E.View内部默认消息派发过程。

           1.回调监听者的onTouch()函数,如果监听者消耗了该消息,则返回true。

           2调用onTouch(),如果该方法没被覆盖的话,该函数内部有默认的执行方式。

               a.判断视图是否处于disable状态,如果是,则返回true。

               b.判断该视图是否是可点击的,如果不可点击,则返回false。否则执行触摸消息的默认处理逻辑。该逻辑中分别处理了ACTION_DOWN,MOVE,UP消息。具体过程如下:

               #1在ACTION_DOWN消息中,给mPrivateFlags变量添加一个PRESSED标识,并将变量mHasPerformLongPress置为false,然后启动tap监测。即发送一个延迟为ViewConfigration.getTapTimeout()的异步消息。

               #2处理MOVE消息,(对触摸消息而言,一次触摸消息只有一个DOWN消息,接下来就是连续的MOVE消息,并最终以UP消息结束。)具体逻辑包括,判断是否移动到了视图区以外,如果是,则删除Tap,或者longPress的监测,并清除mPrivateFlags中的PRESSED标识,然后调用refreshDrawableState()刷新视图的状态。

               #3处理ACTION_UP消息,判断该UP消息是发生在哪一个监测时间段中,并据此进行不同的处理。

                   a.查看是否发生在pre-pressed时间段内,如果是,则给变量pressed赋值为true。

                   b.无论是发生在pre-pressed区还是发生在press区,都应该让该视图获取焦点,前提是该视图在Touch模式下可以拥有焦点。

                   c.如果发生在press之后,即长按,则什么都不做。因为长按的异步消息已经处理了长按消息。如果发生在长按之前,则变量mHasPerformedLongPress为false,此时调用removeLongPressCallBack()移除还没有执行的长按监测消息。

                   d.判断变量focusToken的局部变量是否为false,在一般情况下,都是false。up消息才能导致回调performClick()函数,

                   e.分别处理tap和press动作,如果是press操作,则清除PRESED标识,并改变视图状态,如果是Tap,仅仅发送一个UnSetPressState异步消息。    

                   f.调用removeTapCallback(),关闭tap监测。

       4.处理ACTION_CANCEL消息,清除pressed标识,刷新视图状态,关闭tap监测。


       至此,touch消息的一次处理过程就结束了。

       下一步就是用例子解析自定义View的事件处理过程。



android touch事件学习与理解(一)(android内核剖析),,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。