简记:Touch事件处理

上周面试被问到这个自己觉得简单不会问的问题[哭],回答逻辑混乱,这里看一复习一遍。。

参考:http://blog.csdn.net/lmj623565791/article/details/38960443

参考文章中已经描述的很清楚,这里仅记录下要点加深映像。

View事件处理

首先进入dispatchTouchEvent方法

1.View是在dispatchTouchEvent方法中处理touch事件的,并且是由ViewGroup在dispatchTransformedTouchEvent(..., View child, ...)方法中调用。

2.dispatchTouchEvent中优先判断是否设置了touchListener,如果设置了并且onTouch方法返回true,则不执行后续的onTouchEvent方法,且dispatchTouchEvent方法返回true表当前view要消费这个事件。   如果没有设置onTouchListener或者onTouch方法返回false,则调用view自身的onTouchEvent方法,并且dispatchTouchEvent方法返回onTouchEvent方法的返回值。

进入onTouchEvent方法

3.一开始直接判断当前view如果是disable的并且是clickable或longClickable或contentClickable的,直接返回true.表示当前view消费事件但是并不执行任务操作。

4.接着判断如果touchDelegate不为空并且onTouch方法返回true,则onTouchEvent直接返回true.

5.然后在判断如果当前view是clickable或者longClickable或者contentClickable的,直接返回true,否则直接返回false。这说明不管当前view有没有设置各种click的listener,只要是各种clickable的就会消费当前事件。

6.ACTION_DOWN时,先看代码

a.首先会判断当前view是否在一个可以滚动的viewgroup中,isInScrollingContainer方法会递归往父级找,直到找到某一级viewgroup的shouldDelayChildPressedState方法返回true(ViewGroup类中此方法默认返回true),表示需要延迟一下child的pressed状态设置(通过mPendingCheckForTap),然后通过post一个CheckForTap的runnable来延迟设置pressed状态(如注释描述,这样做为了避免当前用户是要进行scroll操作而不是click),并且这里会把当前状态设置为prepressed状态。  否则如果不在一个scrollingContainer中则直接改变当前view的状态为pressed并且调用checkForLongClick方法post一个CheckForLongPress的runnable对象。

b.看下CheckForTab

首先把prepressed状态清除,状态调用setPressed方法设置为pressed状态,其中会根据press的状态改变设置drawable,并且会调用dispatchSetPressed(boolean pressed)方法。然后在调checkForLongClick方法post一个longClick处理的runnable,并且这里会用longClick的生效时间longPressTimeout500减掉从down事件到现在delay的tapTimeout100.

c.在看CheckForLongPress

从down开始过了500ms后checkForLongPress还没有被remove掉则会调用处理longClick事件。如果当前是pressed状态并且parent为不空,则会调用performLongClick方法,其中会调用到onLongClickListener的onLongClick,并且如果onLongClick返回true则mHasPerformedLongPress会被设置为true.这个变量在up时会用来判断是否需要执行click.

7.的回到onTouchEvent的ACTION_MOVE

这里仅是判断当前move的位置是否在当前view内,如果不在则调用removeTapCallback,清除prepressed状态和remove掉checkForTap的runnable,不管从down到现在是否已经超过100ms(如果超过了也不会有影响)。如果当前是pressed状态则把checkForLongPress也remove掉并且取消pressed状态。

经过上面的两个remove之后,如果从down到现在还不到500,那么根据Up中开头上面的判断,当前即不会是prepressed也不会是pressed状态,所以手指只要move出当前view的范围过,那么click事件就不会在触发了。如果已经超过500,longClick已经触发,那么move只会影响click的触发,不管longClickListener是否存在或者onLongClick返回值是什么。

8.然后是CANCEL事件

正常情况下不会收到cancel事件,而且也只会在当前view之前已经消费了donw事件后才有可能收到,有时候父级控件中有可能需要阻断事件的传递自己消费(即使之前的donw事件已经由子view消费),这时候父级控件就会给之前消费了down事件的子view一个cancel事件让子view恢复之前事件设置的各种状态。

9.最后是UP事件

a.如果当前是prepressed状态(100ms前),直接设置为pressed.

b.如果mHasPerformedLongPress为false,可能是没有到达500ms或者到达了但是没有LongClickListener或onLongClick返回false.则会处理click事件。

c.通过UnsetPressedState来把当前view的pressed状态设置为false.这里如果是还不到100ms会delayPost 64ms清除的操作,猜测是因为从down到up如果还不到100ms(checkForTab没有执行),就在up中手动设置了pressed为true,为了让view从pressed到unpressed有一小段时间间隔(如为了drawable展示切换能被看到)才加的。

d.最后调用removeTapCallback()把checkForTap检查remove掉。这里之前老是想为什么要放到最后执行,万一在onClick中执行时间过长,在执行到这句代码前checkForTap的runnable初始执行了怎么办,后来想起来都是在主线程执行的,不会存在这种问题,放到最前和最后都是一样的。

ViewGroup事件处理

首先入口也是dispatchTouchEvent方法(重写了View的方法),其次是在PhoneWindow中被调用的

这里不在往上查是哪儿调的phonewindow,着重分析ViewGroup的dispatchTouchEvent,代码很长,分块讲解。

1.如果当前是DOWN事件,进行初始化设置。

如注释描述,可能由于app的切换,ANR或者其它状态变化的原因导致framework把上一次手势的UP或者CANCEl事件丢掉了,从而导致在上一次手势中设置的各种状态没有处理完,如果不做初始化会使得这次手势处理混乱。

这个方法中的mFirstTouchTarget代表的是上次手势流程中消费所有touch事件的view。这里初始化会通过dispatchTransformedTouchEvent方法给这个view分发一个cancel事件来让它自己初始化它由上一次手势设置的各种状态。然后通过clearTouchTargets把它回收掉并置空(后面需要重新找到消费这次手势流程的touchTarget)。

2.判断是否需要intercept事件传递。

a.首先由于上一步的原因,这里外层if中的两个条件肯定最多只会有一个为true.

b.如果两个都为false,说明当前不是初始的down事件而且之前的down事件也没有找到需要消费的target,那么直接就在else中把intercepted设为true代表直接阻断不用在去子view中找需要消费事件的view.

c.如果当前为down事件(当前还没有消费事件的mFirstTouchTarget)或者mFirstTouchTarget不为空(说明当前不是down事件,且之前在down时已经找到了消费这轮手势的target),就需要判断下是否需要阻断事件。判断是根据是否可以阻断(disallowIntercept为true代表不能阻断)和是否需要阻断(onInterceptTouchEvent返回true代表需要阻断,默认false)来决定的。


刚刚查了下简书原来可以设置为markdown编辑器的。。无知如我。。但是只能新建文章才能用。。这篇就将就下继续贴图吧。。

3.查找消费手势的子view

仅在当前事件为down并且没有被intercepted时才会执行查找操作(多指触摸不分析了。。)。各种判断不贴了

如注释描述,从前到后扫描子view,找出可以接收事件的子view。这里有两个接口会影响扫描子view时的顺序,一个是buildTouchDispatchChildList(),通过构造一个接收事件分发的子view列表,它里面是根据子view中的z(和x,y对应)属性和自定义的drawingorder来构造的。另外一种影响顺序的方式就是自定义的drawingorder了。

遍历子view列表,判断每个子view是否可以接收触摸事件,看不到的view或者正在执行动画的子view不能接收。并且判断触摸点是否在子view的范围内。

接着通过dispatchTransformedTouchEvent方法,把事件通过子view的dispatchTouchEvent方法传递下去,让子view自己决定是否需要消费事件。如果子view消费了事件就会把child通过addTouchTarget方法设置为之前见过的mFirstTouchTarget(重申这里不考虑多指触摸的情况)。然后直接break跳出查找。到这就找到新的需要消费downg事件的newTouchTarget.

4.其它

1.mFirstTouchTarget == null有三种情况。一种是down事件时查找消费事件的子view没找到,这时直接调dispatchTransformedTouchEvent,因为传的child为空,这时会直接调super.dispatchTouchEvent方法,处理的方式就和上面讲的View的事件处理一样了。另一种情况是还是在down事件但是并没有查找子view(被intercepted了)。第三种是当前不是down事件并且在之前的down事件中没有消费事件的子view(可能是没找到也可能是viewgroup自己处理了),这里能接收到非down事件说明之前的down事件中这个viewgroup的dispatchtouchevent方法肯定返回的true.

2.alreadyDispatchedToNewTouchTarget为true是之前down事件查找消费的子view时子view返回true.代表已经处理过down事件,这里就不用在执行一次了。

3.这里是处理两种情况的,一种是viewgroup想要intercept,所以会给子view分发一个cancel的事件。另一种是把当前事件(非down事件)分发给子view。这里我有点疑惑的是第一种情况下,viewgroup想intercept,在给子view分发一个cancel事件后并没有在调用super.dispatchTouchEvent来自己处理当前事件

总结

差不多就这样了。

1.首先入口都是dispatchTouchEvent方法。

2.viewgroup可以阻断,子view也可以通过requestDisallowInterceptTouchEvent方法设置不让parent阻断,并且这个设置在每次手势流程结束后(不管是UP还是Cancel)都会初始重置。

3.viewgroup如果阻断了子view消费事件会给子view一个cancel.

4.只有在down是viewgroup才会查找子view中需要消费事件的,如果之前已经找到就直接把事件分发给它。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容