android触摸消息的派发过程

来源:互联网 发布:幼儿园美工室活动计划 编辑:程序博客网 时间:2024/05/16 14:38

1.触摸消息是消息获取模块直接派发给应用程序的。
2.触摸消息在处理时, 需要根据触摸坐标计算该消息应该派发给哪个View/ViewGroup, 在案件取消处理中不存在 该计算过程。
3.没有类似”系统按键”的”系统触摸键”, 应用程序可完全控制触摸行为。
4.子视图优先父视图处理消息, 即首先是子视图处理该消息,只有当子视图消耗该消息时, 父视图才有机会处理 该消息。
触摸消息总体派发过程
1.进行物理像素到逻辑像素的转换。
2.如果是DOWN消息, 调用ensureTouchMode(true)函数则进入触摸模式,与之相反的是”非触摸模式”,即按键模式。
3.将屏幕坐标转换到视图坐标。
4.调用mView.dispathTouchEvent()将消息派发给根视图,该函数内部会继而将消息派发到整个View树。
5.如果以上根视图以及其所有子视图都没有消耗该消息,最后处理屏幕边界偏移。屏幕边界偏移在程序中用英文edge slop表示,它的作用是当用户正好触摸到屏幕边界时,系统自动对原始消息进行一定的偏移,然后在新的偏移后的位置上寻找是否有匹配的视图,如果有则将消息派发到该视图。
根视图内部消息派发过程
首先来看mView.dispatchTouchEvent()的派发过程。该函数是在ViewRoot中调用的,mView的类型可能有两种情况,对于应用窗口而言,mView是一个PhoneWindow中的DecorView类型,对于非应用窗口而言,mView是一般的ViewGroup类型。
在DecorView中,首先判断是否存在Callback对象,它和按键消息派发时的Callback对象一样,就是Activity类。如果没有Callback对象,则直接调用DecorView基类ViewGroup中的dispatchTouchEvent()函数。
在Activity中,dispatchTouchEvent()的过程如下。
1、如果ACTION_DOWN消息,则调用onUserInteraction()。
2、调用所包含的Window对象的superDispatchTouchEvent()。
3、如果Window类没有消耗该消息,则调用onTouchEvent()。
下面看看Window类中的superDispatchTouchEvent()。此时Window类的实现就是PhoneWindow类,该函数继而调用mDecor的superDispatchTouchEvent(),而在DecorView的该函数中又调用super.dispatchTouchEvent,即ViewGroup的dispatchTouchEvent函数。注意这里的调用过程,一般的消耗处理流程是当上一步没有消耗消息时才执行下一个处理逻辑,而在跟视图DecorView中,则是当没有Callback时才调用ViewGroup的消息处理逻辑,而不是当Callback没有消耗消息时才调用ViewGroup的消息处理逻辑,原因就是Callback本身就会调用ViewGroup的消息处理逻辑。
ViewGroup的内部消息派发过程
ViewGroup内部的处理逻辑也采用递归方式,但与按键处理的递归有所不同。触摸消息处理中首先会把消息派发给View树中最后一个子视图,如果子视图没有消耗该消息,才递归派发给其父视图,而在按键消息处理时,递归的过程正好相反。
1、将布局左边转化为视图坐标。
为什么要转换呢?因为接下来要判断该坐标落到了该ViewGroup中的哪个子视图中,子视图的位置都是相对于该ViewGroup的视图坐标的。
2、处理DOWN消息,其作用是判断该视图坐标落到了哪个子视图中。
(1)首先判断ViewGroup本身是否被禁止获取Touch消息,如果没有禁止,并且回调函数onInterceptTouchEvent()中没有消耗该消息,则意味着该消息可以传递给子视图。如果子视图消耗了该DOWN消息,则直接返回true。
(2)开始寻找子视图。调用child.getHitRect(frame)函数获取该子视图在父视图中的布局坐标,即该ViewGroup为该child分配的位置是什么,这个位置相对于该child来讲是布局坐标,而相对于ViewGroup来讲却是视图坐标,参数frame是执行完毕后的位置输出矩形。得到位置后,就可以调用frame.contain()方法判断该消息位置是否被包含到了该child中,如果包含,并且该child也是一个ViewGroup,则准备递归调用该child的dispatchTouchEvent(),在调用之前,首先需要把坐标重新转换到child的坐标系中。
(3)完成了递归操作钱的坐标转换工作,接下来判断该child是否是ViewGroup类。如果是就递归调用到ViewGroup的dispatchTouchEvent(),重新从第一步开始执行,如果child不是ViewGroup,而是一个View,则意味着递归调用的结束。
3、如果是UP活着CANCEL消息,则消除mGroupFlags中的FLAG_DISALLOW_INTERCEPT标识,即允许该ViewGroup截获消息。
4、判断target变量是否为空。空代表了所有的子窗口没有消耗该消息,所以该ViewGroup本身需要处理该消息。在第二步中,如果匹配到某个child, 并且该child消耗了消息后,会将该child赋值给父视图中的mMotionTarget变量。在该步中,首先要还原消息的原始位置,因为在第二步中,为了判断子视图是否包含该消息中的位置,对位置进行了从布局坐标到视图坐标的转换,而此时则需要把视图坐标重新转换为布局坐标,因为接下来要调用super.dispatchTouchEvent(),即View类的该函数。View类中处理该函数时,需要布局坐标。转换之后,直接调用super.dispatchTouchEvent(),并返回其执行结果,该函数内部仅仅是回调onTouchEvent(),调用之前先判断mPrivateFlags中是否包含CANCEL_NEXT_UP_EVENT标识,该表示一般情况下不会存在,如果存在,则将消息的action类型改为ACTION_CANCEL。
5、处理target存在,并且变量disallowInercept为false;即允许截获,在默认况下ViewGroup都是允许截获消息的,只有当该ViewGroup的子视图调用父视图的requestDisallowInterceptTouchEvent()函数时,方可禁止父视图再次截获消息。但每次UP消息或者CANCEL消息之后,该Viewgroup又会重新截获信息。注意,在本步中,如果不允许截获消息,那么也就不会调用onInterceptTouchEvent()函数了,如果允许,并且onTerceptTouchEvent()消耗了该消息,才执行本步的操作。
6、在大多数情况下都会执行到该步,即target存在,并且ViewGroup本身不允许截获消息或者允许截获但是却没有消耗消息,于是调用target.dispatchTouchEvent()把该消息继续交给目标视图处理。在调用该函数前需要检查target中是否声明过要取消随后的消息,即mPrivateFlags中包含CANCEL_NEXT_UP_EVENT,如果是,则把消息action值修改为CANCEL,置空mMotionTarget变量,因为target不想处理接下来的消息,那么就可以认为没有target了。
以上就是Touch消息在ViewGroup内部的递归和派发,在这里注意区分onInterceptTouchEvent()和onTouchEvent()。
onInterceptTouchEvent()是在ViewGroup中定义的,即只有ViewGroup的子类能够重载该方法。而onTouchEvent()函数有两个定义,一个是在View类中定义的,所有View类的子类都可以重载该方法,包括ViewGroup,另一个是在Activity中定义的,用户Activity可以重载该函数。View系统的消息处理机制中,会先执行视图内部的onTouchEvent,如果没有处理,才会调用Activity中的onTouchEvent()。
另外,对于ViewGroup而言,在一般情况下会先调用onInterceptTouchEvent(),只有当该函数没有消耗掉消息,并且其包含的子视图也没有消耗掉该消息时,才会执行该ViewGroup的onTouchEvent()。而对于View而言,没有onInterceptTouchEvent()被调用。但并不是所有的消息处理过程都是先调用onInterceptTouchEvent(), 只有两种情况才会调用到onInterceptTouchEvent()。
1.即在以上第2步骤中,当时DOWN消息,并且ViewGroup允许截获消息时。
2.即在以上第5步骤中,当ViewGroup中存在target对象,并且允许截获消息时。
View内默认消息派发过程
1、调用onFilterTouchEventForSecurity() 处理窗口处于模糊显示状态下的消息。所谓的模糊显示是指,应用程序可以设置当前窗口为模糊状态,此时窗口内部的所有视图将显示为模糊效果。这样做的目的是为了隐藏窗口中的内容,对于其中各个视图而言,可以设置该视图的FILTER_TOUCHES_WHEN_OBSCURED标识,如果存在该标识,则意味着用户希望不要处理该消息。
2、回调视图监听者的onTouch()函数,如果监听者消耗了该消息,则直接返回。
3、调用onTouchEvent(),应用程序可以重载该函数,但如果没有重载的话,该函数内部有默认的执行方式,默认的执行流程如下:
(1)判断该视图是否为disable状态,如果是,什么都不做,返回true,即消耗该消息
(2)处理消息代理TouchDelegate。所谓的消息代理是指,可以给某个View指定一个消息处理代理,当View收到消息时,首先将该消息派发给其代理进行处理。如果代理内部消耗了该消息,则View不需要再进行任何处理;如果代理没有处理,则View继续按照默认的逻辑进行处理。该类的目的是为了扩大点击区。(没有实现)
(3)判断该视图是否是可以点击的,如果不可点击,则直接返回false,即不处理该消息。否则,真正开始执行触摸消息的默认处理逻辑,该逻辑中分别处理了ACTION_DOWN、MOVE和UP消息,
1⃣️在ACTION_DOWN消息中,给mPrivateFlags变量添加PRESSED标识,并将变量mHasPerformLongPress置为false,然后启动tap监测,即发送一个异步延迟消息,延迟时间为ViewConfigration.getTapTimeout()。
2⃣️对于触摸消息而言,消息本身没有repeat的属性,这与按键消息不同,一次触摸消息只有一个DOWN消息,接下来就是连续的MOVE消息,并最终以UP消息结束。
3⃣️处理ACTION_UP消息,代码中判断该UP消息是发生在哪一个检测时间段中,并据此进行不同的处理。
4、处理ACTION_CANCEL消息,这里只需要清除PRESSED标识,并改变视图状态,然后再关闭tap检测即可。
至此Touch消息的一次处理过程就结束了。

0 0