Moosphan/Android-Daily-Interview

2019-04-22:谈谈Android的事件分发机制?

Moosphan opened this issue · 16 comments

2019-04-22:谈谈Android的事件分发机制?

Android:你摸我,我就给你发。你摸我上面上个Activity,我就先传递到我的中间Viewgroup,然后它再到最下面,最私密的地方View,然后就像小溪流水一样,分给这边,分给那边。

Android:你摸我,我就给你发。你摸我上面上个Activity,我就先传递到我的中间Viewgroup,然后它再到最下面,最私密的地方View,然后就像小溪流水一样,分给这边,分给那边。

老司机,我要下车

zwonb commented

当点击的时候,会先调用顶级viewgroup的dispatchTouchEvent,如果顶级的viewgroup拦截了此事件(onInterceptTouchEvent返回true),则此事件序列由顶级viewgroup处理。如果顶级viewgroup设置setOnTouchListener,则会回调接口中的onTouch,此时顶级的viewgroup中的onTouchEvent不再回调,如果不设置setOnTouchListener则onTouchEvent会回调。如果顶级viewgroup设置setOnClickListener,则会回调接口中的onClick。如果顶级viewgroup不拦截事件,事件就会向下传递给他的子view,然后子view就会调用它的dispatchTouchEvent方法。

Android:你摸我,我就给你发。你摸我上面上个Activity,我就先传递到我的中间Viewgroup,然后它再到最下面,最私密的地方View,然后就像小溪流水一样,分给这边,分给那边。

老司机,我要下车

open car

1.触发过程:Activity->Window->DocerView->ViewGroup->View,View不触发再返回由父级处理依次向上推。
2.在每个阶段都要经过三个方法 dispatchTouchEvent(分发)、onInterceptTouchEvent(拦截)、onTouch(处理)

大体流程:
Activity中走了Window 的 dispatch,Window 的 dispatch 方法直接走了 DocerView 的 dispatch 方法,DocerView 又直接分发给了 ViewGroup,ViewGroup 中走的是 onInterce 判断是否拦截,拦截的话会走 onTouch 来处理,不拦截则继续下发给 View。到 View 这里已经是最底层了,View 若继续不处理,那就调用上层的 onTouch 处理,上层不处理继续往上推。

推荐一篇介绍非常详细的博客:https://www.jianshu.com/p/38015afcdb58

给大家举个形象的例子吧。ViewGroup相当于老板或者部门管理。View相当于员工。onTouchEvent相当于接业务。dispatechTouchEvent相当于处理任务。onIterceptTouchEvent相当于拦截任务。
先来分配角色

  • ViewGroup老板,或者部门管理有接收任务(onTouchEvent),处理任务(dispatchTouchEvent),拦截任务(oninterceptTouchEvent)的方法
  • View有接收任务(onTouchEvent),处理任务(dispatchTouchEvent)的方法。

现在模拟来业务了(点击事件)
老板(ViewGroup)接到业务(onTouchEvent),他有两个选择

  • 这个任务太轻松了,没必要往下传递我直接自己处理,所以拦截任务(oninterceptTouchEvent),并告诉客户处理结果
  • 这个任务我不擅长,但是X部门比较擅长,我交给(dispatechTouchEvent)X部门
    • 然后X部门领导接到任务,部门管理者有两个选择

      • 自己处理oninterceptTouchEvent,并且告诉老板(返回true),我已经处理了
      • 交给部门其他员工(子View)处理
        • 员工接到任务(onTouchEvent),员工只能处理任务(dispatchTouchEvent)。并把处理结果告诉领导(return true|renturn false)。
    • 领导接到员工的反馈,告诉老板(return true|renturn false)。

  • 老板告诉客服处理结果

责任链模式

  1. 会经过Activity->ViewGroup->view,一次往下传递事件,如果一直不拦截再回调回来。
  2. 主要经过三个方法,dispatchTouchEvent(分发事件),oninterceptTouchEvent(是否拦截View中不存在),onTouchEvent(处理)。
  3. 三个方法的用法是,先用dispatchTouchEvent来分发事件,然后用oninterceptTouchEvent来判断是否拦截该任务(此方法在dispatchTouchEvent内部),如果不拦截直接dispatch向下回调,如果拦截就调用自己的onTouchEvent来处理事件。
  4. 如果由setOnClickListener方法会先执行onClick.

事件来源:input -> window -> ViewRoot -> DecorView -> Activity -> ...
事件处理:责任链模式,先从顶层传到底层,再从底层传回顶层,如果中间被消费或拦截,则流程结束
从Activity.dispatchTouchEvent()开始
--向下--> ViewGroup.dispatchTouchEvent(),可通过onInterceptTouchEvent()拦截
--向下--> View.dispatchTouchEvent()
--向下--> View.onTouchEvent()
--向上--> ViewGroup.onTouchEvent()
--向上--> Activity.onTouchEvent()
注意点:如果View没有消费ACTION_DOWN,后面的其他事件就不会再传过来

1.触发过程:Activity->Window->DocerView->ViewGroup->View,View不触发再返回由父级处理依次向上推。
2.在每个阶段都要经过三个方法 dispatchTouchEvent(分发)、onInterceptTouchEvent(拦截)、onTouch(处理)

大体流程:
Activity中走了Window 的 dispatch,Window 的 dispatch 方法直接走了 DocerView 的 dispatch 方法,DocerView 又直接分发给了 ViewGroup,ViewGroup 中走的是 onInterce 判断是否拦截,拦截的话会走 onTouch 来处理,不拦截则继续下发给 View。到 View 这里已经是最底层了,View 若继续不处理,那就调用上层的 onTouch 处理,上层不处理继续往上推。

推荐一篇介绍非常详细的博客:https://www.jianshu.com/p/38015afcdb58

View是没有onInterceptTouchEvent(拦截)的方法的

之前写过一篇文章回顾时间分发机制,希望能有补充。
深入理解Android事件分发机制

1.当我们按下手指,ViewGroup的onInterceptTouchEvent会最先处理down事件。
假设1:onInterceptTouchEvent返回false,子view的onTouchEvent返回true,后续的move和up事件都将传到onInterceptTouchEvent中,然后一层一层的传递下去交给子view处理
假设2:onInterceptTouchEvent返回false,子view的onTouchEvent返回的是false,则后续的move和up事件都将交给ViewGroup的onTouchEvent处理,如果ViewGroup的onTouchEvent返回为true,ViewGroup将会自己处理,如果ViewGroup的onTouchEvent返回false,则会继续向上传递,交给ViewGroup的父亲来处理。
假设3:onInterceptTouchEvent返回true,那么down和后续的move和up事件将会交给ViewGroup的onTouchEvent处理

当顶级的ViewGroup接收到事件消息之后,会调用dispatchTouchEvent 方法进行分发事件,
在分发时会先去判断该事件是否拦截onInterceptTouchEvent。如拦截,则事件不继续进行
分发,自己进行消费。如不拦截,事件继续进行向下传递到子控件,如果子控件是ViewGroup
则继续走上述操作,如果是View则会走onTouchEvent方法。
其中ViewGroup不拦截之后会先走onTouch方法,该方法会影响其onTouchEvent。整体传递是一个责任链模式

事件分发的细节真的非常多,用老板员工的例子,最早开始看的时候我是没记住过。如果你只是看别人得出来的结论,那么十个人他能总结出十种自己的结论,看似好像明白了,过一段时间自己去看还是会忘记,所以我觉得想要真的明白就得自己去跟踪源码,然后用关键代码提炼成伪代码,打log看结果,做好笔记。由于你没法获得自己手机的系统源码,所以最好用模拟器来调试,用模拟器对应的sdk版本编译运行代码,自定义几个简单的View和ViewGroup,从ViewGroup的dispatchTouchEvent()方法处开始打断点。这里分享一个小技巧,如果你想要程序在执行到你自定义ViewGroup的dispatchTouchEvent()处暂停,可以在断点的condition处加上this.getClass().getSimpleName().equals("CustomViewGroup1");这样的条件语句。后续总结完毕,我会发文章出来给大家参考的,大家有问题可以互相交流,共同进步。

事件分发机制是责任链模式,先从顶层传到底层,再从底层传回顶层,如果中间被消费或拦截,则流程结束:
Activity dispatchTouchEvent- >ViewGroup dispatchTouchEvent ->ViewGroup onInterceptTouchEventview->View dispatchTouchEvent -> ->View onTouchEvent-> ViewGroup onTouchEvent->Activity onTouchEvent
事件监听分别有
dispatchTouchEvent(分发)onInterceptTouchEvent(拦截)onTouchEvent(响应)事件
Activity和view只有dispatchTouchEvent(分发)onTouchEvent(响应)事件
因为View没有子View,所以不需要拦截事件。而ViewGroup里面可以包裹子View,所以通过onInterceptTouchEvent方法
在dispatchTouchEvent中调用super.dispatchTouchEvent有底层执行底层dispatchTouchEvent 没有底层执行本层的onTouchEvent响应事件 dispatchTouchEvent返回true则表示响应事件不上发 返回false继续上发执行顶层onTouchEvent

① 驱动层 -> Framework 层 : 用户触摸 , 或按键 后 , 事件在硬件中产生 , 从 硬件驱动层 , 传递到 Framework 层 ;

② WMS -> View 层 : WindowManagerService ( 简称 WMS ) 将事件传递到 View 层 ;

③ View 层内部 : 事件在 View 的容器及下层容器 / 组件 之间传递 ;

https://cloud.tencent.com/developer/article/1154110