共计 6006 个字符,预计需要花费 16 分钟才能阅读完成。
前言
很快乐遇见你~
本文是事件散发系列的第三篇。
在前两篇文章中,Android 事件散发机制一:事件是如何达到 activity 的?剖析了事件散发的真正终点:viewRootImpl,Activity 只是其中的一个环节;Android 事件散发机制二:viewGroup 与 view 对事件的解决 源码解析了 viewGroup 和 view 是如何散发事件的。
事件散发的核心内容,则为 viewGroup 和 view 对事件的散发,也就是第二篇文章。第二篇文章对源码的剖析较为深刻,不足一个更高的角度来扫视事件散发流程。本文在后面的剖析根底上,对整个事件散发的工作流程进行一个总结,更好地把握事件是如何在不同的对象和办法之间进行传递。
回顾
先来回顾一下整体的流程,以便更好地定位咱们的常识。
- 触摸信息从手机触摸屏幕时产生,通过 IMS 和 WMS 发送到 viewRootImpl
- viewRootImpl 通过调用 view 的 dispatchPointerEvent 办法把触摸信息传递给 view
- view 通过调用本身的 dispatchTouchEvent 办法开始了事件散发
图中的 view 指的是一个控件树,他能够是一个 viewGroup 也能够是一个简略的 view。因为 viewGroup 是继承自 view,所以一个控件树,也能够看做是一个 view。
咱们明天探讨的工作流程,就是从图中的 view 调用本身的 dispatchTouchEvent 开始。
次要对象与办法
事件散发的对象
这一部分内容在第二篇有具体解析,这里做个简略的回顾。
当咱们手机触碰屏幕时会产生一系列的 MotionEvent 对象,依据触摸的状况不同,这些对象的类型也会不同。具体如下:
- ACTION_DOWN: 示意手指按下屏幕
- ACTION_MOVE: 手指在屏幕上滑动时,会产生一系列的 MOVE 事件
- ACTION_UP: 手指抬起,来到屏幕、
- ACTION_CANCEL:当出现异常状况事件序列被中断,会产生该类型事件
- ACTION_POINTER_DOWN: 当曾经有一个手指按下的状况下,另一个手指按下会产生该事件
- ACTION_POINTER_UP: 多个手指同时按下的状况下,抬起其中一个手指会产生该事件
事件散发的办法
事件散发属于控件零碎的一部分,次要的散发对象是 viewGroup 与 view。而其中外围的办法有三个:dispatchTouchEvent
、onInterceptTouchEvent
、onTouchEvent
。那么在讲散发流程之前,先来介绍一下这三个办法。这三个办法属于 view 体系的类,其中 Window.CallBack 接口中蕴含了 dispatchTouchEvent
和 onTouchEvent
办法,Activity 和 Dialog 都实现了 Window.CallBack 接口,因而都实现了该办法。因这三个办法常常在自定义 view 中被重写,以下的剖析,如果没有非凡阐明都是在默认办法实现的状况下。
dispatchTouchEvent
该办法是事件散发的外围办法,事件散发的逻辑都是在这个办法中实现。该办法存在于类 View 中,子类 ViewGroup、以及其余的实现类如 DecorView 都重写了该办法。
无论是在 viewGroup 还是 view,该办法的次要作用都是处理事件。如果胜利解决则返回 true,解决失败则返回 false,示意事件没有被解决。具体到类,在 viewGroup 相干类中,该办法的次要作用是把事件散发到该 viewGroup 所领有的子 view,如果子 view 没有解决则本人解决;在 view 的相干类中,该办法的次要作用是生产触摸事件。
onInterceptTouchEvent
该办法只存在于 viewGroup 中,当一个事件须要被散发到子 view 时,viewGroup 会调用此办法查看是否要进行拦挡。如果拦挡则本人解决,而如果不拦挡才会调用子 view 的 dispatchTouchEvent
办法散发事件。
办法返回 true 示意拦挡事件,返回 false 示意不拦挡。
这个办法默认只对鼠标的相干操作的一种非凡状况进行了拦挡,其余的状况须要具体的实现类去重写拦挡。
onTouchEvent
该办法是生产事件的次要办法,存在于 view 中,viewGroup 默认并没有重写该办法。办法返回 true 示意生产事件,返回 false 示意不生产事件。
viewGroup 散发事件时,如果没有一个子 view 生产事件,那么会调用本身的 onTouchEvent 办法来处理事件。View 的 dispatchTouchEvent 办法中,并不是间接调用 onTouchEvent 办法来生产事件,而是先调用 onTouchListener 判断是否生产;如果 onTouchListener 没有生产事件,才会调用 onTouchEvent 来处理事件。
咱们为 view 设置的 onClickListener 与 onLongClickListener 都是在 View 的 dispatchTouchEvent 办法中,依据具体的触摸状况被调用。
重要规定
事件散发有一个很重要的准则:一个触控点的事件序列只能给一个 view 生产,除非产生异常情况如被 viewGroup 拦挡 。具体到代码实现就是: 生产了一个触控点事件序列的 down 事件的 view,将继续生产该触控点事件序列接下来的所有的事件。举个栗子:
当我手指按下屏幕时产生了一个 down 事件,只有一个 view 生产了这个 down 事件,那么接下来我的手指滑动屏幕产生的 move 事件会且仅会给这个 view 生产。而当我手机抬起,再按下时,这时候又会产生新的 down 事件,那么这个时候就会再一次去寻找生产 down 事件的 view。所以,事件散发,是以事件序列为单位的。
因而上面的工作流程中都是指down 事件的散发,而不是 ACTION_MOVE 或 ACTION_UP 的散发。因为生产了 down 事件,意味着接下来的 move 和 up 事件都会给这个 view 解决,也就无所谓散发了。但同时留神事件序列是能够被 viewGroup 的 onInterceptTouchEvent 中断的,这些就属于其余的状况了。
仔细的读者还会发现事件散发中蕴含了多点触控。在多点触控的状况下,ACTION_POINTER_DOWN 与 ACTION_DOWN 的散发规定是不同的,具体可返回第二篇文章理解具体。ACTION_POINTER_DOWN 在 ACTION_DOWN 的散发模型上稍作了一些批改而已,前面会具体解析,
工作流程模型
工作流程模型,实质上就是不同的控件对象,viewGroup 和 view 之间事件散发办法的关系。须要留神的是,这里探讨的是 viewGroup 和 view 的默认办法实现,不波及其余实现类如 DecorView 的重写办法。
上面用一段伪代码来示意三个事件散发办法之间的关系(这里再次强调,这里的事件散发模型散发的事件类型是 ACTION_DOWN 且都是默认的办法,没有通过重写,这点很重要):
public boolean dispatchTouchEvent(MotionEvent event){
// 先判断是否拦挡
if (onInterceptTouchEvent()){
// 如果拦挡调用本身的 onTouchEvent 办法判断是否生产事件
return onTouchEvent(event);
}
// 否则调用子 view 的散发办法判断是否处理事件
if (childView.dispatchTouchEvent(event)){return true;}else{return onTouchEvent(event);
}
}
这段代码十分好的展现了三个办法之间的关系:在 viewGroup 收到触摸事件时,会先去调用 onInterceptTouchEvent
办法判断是否拦挡,如果拦挡则调用本人的 onTouchEvent
办法处理事件,否则调用子 view 的 dispatchTouchEvent
办法来散发事件。因为子 view 也有可能是一个 viewGroup,这样就造成了一个相似递归的关系。
这里我再补上 view 散发逻辑的简化伪代码:
public boolean dispatchTouchEvent(MotionEvent event){
// 先判断是否存在 onTouchListener 且返回值为 true
if (mOnTouchListener!=null && mOnTouchListener.onTouch(event)){
// 如果胜利生产则返回 true
return true;
}else{
// 否则调用 onTouchEvent 生产事件
return onTouchEvent(event);
}
}
view 与 viewGroup 不同的是他不须要散发事件,所以也就没有必要拦挡事件。view 会先查看是否有 onTouchListener 且返回值是否为 true,如果是 true 则间接返回,否则调用 onTouchEvent 办法来处理事件。
基于上述的关系,能够失去上面的工作流程图:
这里为了展现类递归关系应用了画了两个 viewGroup,只需看两头一个即可,上面对这个图进行解析:
-
viewGroup
-
viewGroup 的 dispatchTouchEvent 办法接管到事件音讯,首先会去调用 onInterceptTouchEvent 判断是否拦挡事件
- 如果拦挡,则调用本身的 onTouchEvent 办法
- 如果不拦挡则调用子 view 的 dispatchTouchEvent 办法
- 子 view 没有生产事件,那么会调用 viewGroup 自身的 onTouchEvent
- 下面 1、2 步的处理结果为 viewGroup 的 dispatchTouchEvent 办法的处理结果,并返回给上一层的 onTouchEvent 解决
-
-
view
- view 的 dispatchTouchEvent 默认状况下会调用 onTouchEvent 来处理事件,返回 true 示意生产事件,返回 false 示意没有生产事件
- 第 1 步的后果就是 dispatchTouchEvent 办法的处理结果,胜利生产则返回 true,没有生产则返回 false 并交给上一层的 onTouchEvent 解决
能够看到整个工作流程就是一个“U”型构造,在不拦挡的状况下,会一层层向下寻找生产事件的 view。而如果以后 view 不处理事件,那么就一层层向上抛,寻找解决的 viewGroup。
上述的工作流程模型并不是残缺的,还有其余的非凡状况没有思考。上面探讨几种非凡的状况:
事件序列被中断
咱们晓得,当一个 view 接管了 down 事件之后,该触控点接下来的事件都会被这个 view 生产。然而,viewGroup 是能够在中途掐断事件流的,因为每一个须要分发给子 view 的事件都须要通过拦挡办法:onInterceptTouchEvent
(当然,这里不探讨子 view 设置不拦挡标记的状况)。那么当 viewGroup 掐断事件流之后,事件的走向又是如何的呢?参看下图:(留神,这里不探讨多指操作的状况,仅探讨单指操作的 move 或 up 事件被 viewGroup 拦挡的状况)
- 当 viewGroup 拦挡子 view 的 move 或 up 事件之后,会将以后事件改为 cancel 事件并发送给子 view
- 如果以后事件序列还未完结,那些接下来的事件都会交给 viewGroup 的 ouTouchEvent 解决
-
此时不论是 viewGroup 还是 view 的 onTouchEvent 返回了 false,那么将导致整个控件树的 dispatchTouchEvent 办法返回 false
- 秉承着一个事件序列只能给一个 view 生产的准则,如果一个 view 耗费了 down 事件却在接下来的 move 或 up 事件返回了 false,那么此事件不会给下层的 viewGroup 解决,而是间接返回 false。
多点触控状况
下面探讨的所有状况,都是不蕴含多点触控状况的。多点触控的状况,在原有的事件散发流程上,新增了一些非凡状况。这里就不再画图,而是把一些非凡状况形容一下,读者理解一下就能够了。
默认状况下,viewGroup 是反对多点触控的散发,但 view 是不反对多点触控的,须要本人去重写 dispatchTouchEvent
办法来反对多点触控。
多点触控的散发规定如下:
viewGroup 在已有 view 承受了其余触点的 down 事件的状况下,另一个手指按下产生 ACTION_POINTER_DOWN 事件传递给 viewGroup:
-
viewGroup 会依照 ACTION_DOWN 的形式去散发 ACTION_POINTER_DOWN 事件
- 如果子 view 生产该事件,那么和单点触控的流程统一
- 如果子 view 未生产该事件,那么会交给上一个最初接管 down 事件的 view 去解决
- viewGroup 两个 view 接管了不同的 down 事件,那么拦挡其中一个 view 的事件序列,viewGroup 不会生产拦挡的事件序列。换句话说,viewGroup 和其中的 view 不能同时接管触摸事件。
Activity 的事件散发
仔细的读者会发现,上述的工作流程并不波及 Activity。咱们印象中的事件散发都是 Activity -> Window -> ViewGroup
,那么这是怎么回事?这所有,都是 DecorView“惹的祸”。
DecorView 重写 viewGroup 的 dispatchTouchEvent
办法,当接管到触摸事件后,DecorView 会首先把触摸对象传递给外部的 callBack 对象。没错,这个 callBack 对象就是 Activity。退出 Activity 这个环节之后,散发的流程如下图所示:
整体上和后面的流程没有多大的不同,Activity 继承了 Window.CallBack 接口,所以也有 dispatchTouchEvent 和 onTouchEvent 办法。对上图做个简略的剖析:
- activity 接管到触摸事件之后,会间接把触摸事件分发给 viewGroup
- 如果 viewGroup 的 dispatchTouchEvent 办法返回 false,那么会调用 Activity 的 onTouchEvent 来处理事件
- 第 1、2 步的处理结果就是 activity 的 dispatchTouchEvent 办法的处理结果,并返回给下层
下面的流程不仅实用于 Activity,同样实用于 Dialog 等应用 DecorView 和 callback 模式的控件零碎。
最初
到这里,事件散发的次要内容也就解说完了。联合前两篇文章,置信读者对于事件散发有更高的认知。
纸上得来终觉浅,绝知此事要躬行。学了常识之后最重要的就是实际。下一篇文章将简略剖析一下如何利用学习到的事件散发常识使用到理论开发中。
原创不易,你的点赞是我创作最大的能源,感激浏览 ~
全文到此,原创不易,感觉有帮忙能够点赞珍藏评论转发。
笔者满腹经纶,有任何想法欢送评论区交换斧正。
如需转载请评论区或私信交换。另外欢迎光临笔者的集体博客:传送门