请尊重分享成果,转载请注明出处:
/hejjunlin/article/details/52298780
上篇分析了View的事件分发流程,留了一个问题:如果上面的EventButton继承TextView的话,按下抬起,会有一个现象,我可以告诉大家现象:就是只有dispatchTouchEvent ACTION_DOWN,onTouch ACTION_DOWN,onTouchEvent ACTION_DOWN这三个,你移动,或者抬起,是没有MOVE,或者UP的。现在说下答案:当时我们在MainActivity中用到了 EventButton.setOnTouchListener(OnTouchListener l)事件监听,返回的是false,表示啥意思,ACTION_DOWN一次支持到switch中去,直接return了false,告诉父view,我处理不了,你不要派任务(事件传递)给我了,所以它只接到了ACTION_DOWN事件,接不到其他的MOVE或者UP,那问题来了,为啥继承Button可以呢?看Button源码,发现啥也没干,就继承了TextView,但用了自己的 com.android.internal.R.attr.buttonStyle,进去一看,发现两个点,一是focusable是true的,二是android:clickable也是true的,这可是问题的答案呢,可查看Android View框架总结(二)View焦点,如何是isfocusable的话,就能先于父view获取到事件。所以它是有MOVE和UP的。有兴趣的,可以把MainActivity中EventButton.setOnTouchListener(OnTouchListener l)事件监听返回true,这个EventButton就能有MOVE和UP了,可以自己动手试试,你看到的才是答案。
<style name="Widget.Button"> <item name="android:background">@android:drawable/btn_default</item> <item name="android:focusable">true</item> <item name="android:clickable">true</item> <item name="android:textSize">20sp</item> <item name="android:textStyle">normal</item> <item name="android:textColor">@android:color/button_text</item> <item name="android:gravity">center_vertical|center_horizontal</item> </style>
前面是上篇遗留的问题,本篇开始分析ViewGroup事件分发(PS:本篇文章中源码均是android 6.0,请知晓)
dispatchTouchEventonInterceptTouchEventonTouchEventViewGroup 事件的分发机制流程图案例案例流程图
dispatchTouchEvent
ViewGroup.java -> dispatchTouchEvent()
onInterceptTouchEvent
ViewGroup.java -> onInterceptTouchEvent()
虽然以上代码都有注释,但是还有几个地方说明下:
if (!canViewReceivePointerEvents(child)
这里表示判断当前的down、POINTER_DOWN、HOVER_MOVE三个事件的坐标点是否落在了子控件上,如果落在子控件上,if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))通过dispatchTransformedTouchEvent传递事件,交由子控件判断是否传递或自己消费处理。如果dispatchTransformedTouchEvent返回true,表示子控件已消费处理,并添加此子控件View到触摸链表,并放置链表头,并结束遍历子控件。newTouchTarget = addTouchTarget(child, idBitsToAssign);false表示未处理。
onInterceptTouchEvent表示是否拦截当前事件,返回true表示拦截,如果拦截了事件,那么将不会分发给子View。如:ViewGroup拦截了这个事件,那么所有事件都由该ViewGroup处理,它内部的子View将不会获得事件的传递。ViewGroup是默认不拦截事件的,注意:View是没有这个方法的,也即是说,继承自View的一个子View不能重写该方法,也无需拦截事件,因为它下面没有View了,它要么处理事件要么不处理事件,所以最底层的子View不能拦截事件。
子View消耗了ACTION_DOWN事件,DOWN,MOVE,UP都是一个回合的事件,
DOWN -> onInterceptTouchEvent -> true 就不向下传事件,且onInterceptTouchEvent不再调用
DOWN -> onInterceptTouchEvent -> false 传下去
MOVE -> onInterceptTouchEvent -> true 不传
每个回合都要经历ViewGroup中的onInterceptTouchEvent的判断是否要拦截接下来的这个子View的事件(当然注意前提条件是每次都是返回false不拦截时,打个不恰当的比喻:这个就好像你买六合彩一样,要是你中了大奖,你至少一段时间内,你都不会再买了,如果没中,那就每次都要买);或者,如果第一次DOWN事件后,由ViewGroup本身消费掉了,那mFirstTouchTarget就为null,为空的话,说明ViewGroup不想这个事件传到它的子view中去,此后MOVE或者UP事件都将在ViewGroup这层自己处理,不会传到子view中去。
打个比喻(对照事件传递机制来),国家(中央)发了一份电报到省里面(相当于一次事件DOWN,MOVE,UP其一都和),关于贫困补助某地区的,如果钱比较多,省里面觉得这油水得捞一捞(拦截onInterceptTouchEvent 返回true),于是把这个事件拦截下,自己消费掉了,直接return true,告诉上面,这个事件消耗完毕,反正上面也不知道(暂且这么说吧),下一次,国家(中央)又发了一份电报到省里面,,关于贫困补助某地区的,这次钱比较少,省里面觉得没啥捞的(不拦截 拦截onInterceptTouchEvent 返回false),就把这个消息原封不动发向了地方政府,地方政府消费这个事件,然后返回true。
dispatchTransformedTouchEvent
ViewGroup.java -> dispatchTransformedTouchEvent()
当传递进来的子View不为null时,就会调用子View的dispatchTouchEvent(event)方法进行事件分发,事件交给子View处理,也即是说,子Viwe符合父view不拦截条件时,事件就会在这里传递给了子View来处理,完成了ViewGroup到子View的事件传递,上面多次调用child.dispatchTouchEvent,这其实就回到上一篇文章中view.dispatchTouchEvent中,而昨天已经分析,view.dispatchTouchEvent接下来会调用view.onTouchEvent返回处理结果,当view事件处理完毕,就会返回一个handled给ViewGroup,就是告诉ViewGroup,是否它消费了这个事件,假如子View的onTouchEvent()返回true,那么就是消耗了事件。
ViewGroup.java -> addTouchTarget()
要是觉得细节太琐碎,总结成下面一张图,看清上面的过程:
案例:
MainActivity
自定义的RelativeLayout
自定义button
布局文件
运行studio,点击按钮,输出log结果:
从log中可以看出:执行过程是从CustomRelativeLayout的dispatchTouchEvent ->CustomRelativeLayout的onInterceptTouchEvent -> EventButton的dispatchTouchEvent ->EventButton的onTouchEvent
在View上触发事件,最先捕获到事件的为View所在的ViewGroup,然后才会到View自身~
同样这个案例,总结成下面一张图,看清上面的过程:
总结
1、一个事件传递从ACTION_DOWN开始,中间有若一个或者多个ACTION_MOVE,最后以ACTION_UP结束。
2、ViewGroup默认不拦截任何事件,所以事件能正常分发到子View处(如果子View符合条件的话),如果没有合适的子View或者子View不消耗ACTION_DOWN事件,那么接着事件会交由ViewGroup处理,并且同一事件序列之后的事件不会再分发给子View了。如果ViewGroup的onTouchEvent也返回false,即ViewGroup也不消耗事件的话,那么最后事件会交由Activity处理。即:逐层分发事件下去,如果都没有处理事件的View,那么事件会逐层向上返回。
3、如果某一个View拦截了事件,那么同一个事件序列的其他所有事件都会交由这个View处理,此时不再调用View(ViewGroup)的onIntercept()方法去询问是否要拦截了。
4、子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦截
5、ViewGroup自身并没有onTouchEvent方法,在dispatchTouchEvent时,会调用dispatchTransformedTouchEvent分发到子view中去,触发ouTouchEvent,但是我们自定义View,继承ViewGroup时,是可以重写ViewGroup的onTouchEvent方法,这是为什么呢?因为ViewGroup继承View,而View是有onTouchEvent方法,所以自定义View继承ViewGroup时,就间接用到了父类的父类中的方法。这样的方处是,对于ViewGroup来说,只是负责事件分发,如果有人继承它,说明想自己控制一些事件发分流程,那么自然相关方法也都可以重写。
第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。
如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易