写点什么

一个 view 事件分发,面试官 6 连问直击灵魂,我被虐的体无完肤

用户头像
Android架构
关注
发布于: 刚刚

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);target.next = mFirstTouchTarget;mFirstTouchTarget = target;return target;}复制代码


紧接着在在后面又会调用了:



这句只有在 view/viewgroup 的dispatchTouchEvent返回 false 的时候,才会走这里,所以后面的action_moveaction_up都会走这里,而此时传入的 child=null,从上面代码可以看到,直接调用了父类的dispatchTouchEvent方法。所以从这里不难看出在 view/viewgroup 的dispatchTouchEvent返回 false 的时候直接调用了父类的dispatchTouchEvent方法,因此只有 action_down 事件。

面试官:如果我只想有 view 的拖拽事件,而不想要 view 的点击事件,让你重写这个 view 的拖拽怎么设计

其实这道题考察大家对 view 的 dispatchTouchEvent 和 view 的 onTouchEvent 事件的处理流程,上面已经分析了想要 view 能执行到 view 的 touch 事件,那么必须要求 view 的dispatchTouchEvent返回 true,而dispatchTouchEvent返回 true 要么是dispatchTouchEvent直接返回 true 或者 view 的onTouchEvent返回 true。如果从效率上看,直接将dispatchTouchEvent返回 true 就 ok,而不需要再去关心onTouchEvent方法。

viewgroup 拦截

关于拦截无非就是拦截或不拦截,而拦截的条件是返回 true,不拦截是返回 false 或返回 super.onInterceptTouchEvent,默认的 super 是返回 false 的,因此可以用 super 表示不拦截


viewgroup 拦截实际是通过在dispatchTouchEvent方法中,设置 intercepted 变量,如果在拦截方法里面返回 true,那么 intercepted 为 true,如果为 true 则在 action_down 的时候 mFirstTouchTarget=null,那么此时是直接调用dispatchTransformedTouchEvent传入的 child=null,因此将事件交给了super.dispatchTouchEvent,此时把它当成一个 view 来处理了。

面试官:有个 viewgroup,里面有个 view,如果 view 在 dispatchTouchView 中不分发事件,并且只在 action_move 中拦截 touch 事件向下分发,说说 viewgroup 到 view 的各个 action 是如何分发的?

分析

先贴出事例代码:




testView 在 testViewgroup 里面,testViewgroup 在 action_move 的时候拦截(onInterceptTouchEvent 在 move 返回 true),testView 不进行分发(dispatchTouchEvent 返回 true) 咋们通过 log 来看结果:



这里执行到 TestViewgroup#dispatchTouchEvent 的 action_move


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


之后就执行了 TestView#dispatchTouchEvent 的 action_cancel,然后后面执行 TestViewgroup#dispatchTouchEvent 和 TestViewgroup#onTouchEvent 的 action_move 和 action_up。 从前面 viewgroup 的 dispatchTouchEvent 分析知道,如果 viewgroup 在 action_down 中发现有子 view 的 dispatchTouchEvent 返回 true,则mFirstTouchTarget不为空,紧接着在 action_move 的时候进行了拦截,则 intercepted=true,既然在 move 过程中确定了 intercepted=true,mFirstTouchTarget 不为空,则可以看 viewgroup.dispatchTouchEvent 部分代码:


TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;//alreadyDispatchedToNewTouchTarget=falseif (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {//由于 move 过程中 intercepted=true,则 cancelChild=truefinal boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;//看到了没这里就是触发 child 的 dispatchTouchEvent 的 action_cancelif (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}if (cancelChild) {if (predecessor == null) {//由于 next=null,因此 mFirstTouchTarget=null,所以在 action_move 刚进来的时候 mFirstTouchTarget=null 了,//待会我们通过反射看下该变量 mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}}predecessor = target;target = next;}复制代码


上面也说明了在 action_move 进来的时候先是触发了 testView#dispatchTouchEvent 的 action_cancel,紧接着mFirstTouchTarget=null 了,由于mFirstTouchTarget是 viewgroup 类中私有的变量,我们可以通过反射调用该变量看下是否为空:


//反射代码,关于反射可以看我之前的文章java反射整理



接着在 testViewgroup#dispatchTouchEvent 中获取mFirstTouchTarget属性:



通过上面可以验证刚才 move 过程中mFirstTouchTarget为空的判断,日志如下:



看到了没,第一次 move 的时候mFirstTouchTarget还不是 null,第二次 move 的时候就是 null 了,因此在后续的 move 和 up 过程中,只会此处:



看到了没,这里传进去的 child=null,根据上面分析可知,当为 null 的时候,只会触发 super.dispatchTouchEvent,所以到了第二次的 move 之后,只能看到 TestViewgroup 的 action_move 和 action_up 了。


所以针对上面面试官提的问题,大家知道怎么说了吧,还是针对该问题做个小结:

小结

先是 down 事件会经过 viewgroup 的 dispatchTouchEvent,再到 viewgroup 的 onInterceptTouchEvent,最后到 view 的 dispatchTouchEvent,此时 mFirstTouchTarget 不为空,紧接着到了 move 首先到 viewgroup 的 dispatchTouchEvent,再到 viewgroup 的 onInterceptTouchEvent,由于在 move 过程中拦截了,紧接着走 view 的 dispatchTouchEvent 的 action_cancel,此时接着把 mFirstTouchTarget 至为 null,因此后续的 move 和 up 事件只会走 viewgroup 的 dispatchTouchEvent 和 onTouchEvent。 画出一张图来给大家看下:



好了,关于这个问题告一段落了,如果分析有问题,大家可以提出疑问。

面试官:里面的 view 在 onTouchEvent 中消费,然后拖动手指,直到手指从其他他 viewgroup 上挪开手指。

其实这个问题在上面分析中已经分析过了,testview 的 onTouchEvent 中消费,所以在 action_down 中mFirstTouchTarget不为空,因此在 action_move 和 action_up 中mFirstTouchTarget还是不为空,所以不管手指是否已经离开了 testview,action_move 和 action_up 还是会走 testview 的 dispatchTouchEvent 和 onTouchEvent。

小结

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
一个view事件分发,面试官6连问直击灵魂,我被虐的体无完肤