前言
- Android事件分发机制是每个Android开发者必须了解的基础知识
- 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全、思路不清晰、无源码分析、简单问题复杂化等等
今天,我将全面总结Android的事件分发机制,我能保证这是市面上的最全面、最清晰、最易懂的
- 本文秉着“结论先行、详细分析在后”的原则,即先让大家感性认识,再通过理性分析从而理解问题;
- 所以,请各位读者先记住结论,再往下继续看分析;
文章较长,阅读需要较长时间,建议收藏等充足时间再进行阅读
目录
1. 基础认知
1.1 事件分发的对象是谁?
答:事件
当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。
Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
主要发生的Touch事件有如下四种:
- MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
- MotionEvent.ACTION_MOVE:滑动View
- MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
- MotionEvent.ACTION_UP:抬起View(与DOWN对应)
事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件
任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图:
即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理,
1.2 事件分发的本质
答:将点击事件(MotionEvent)向某个View进行传递并最终得到处理
即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
1.3 事件在哪些对象之间进行传递?
答:Activity、ViewGroup、View
一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的
View是所有UI组件的基类
一般Button、ImageView、TextView等控件都是继承父类View
ViewGroup是容纳UI组件的容器,即一组View的集合(包含很多子View和子VewGroup),
- 其本身也是从View派生的,即ViewGroup是View的子类
- 是Android所有布局的父类或间接父类:项目用到的布局(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类。
- 与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。
1.4 事件分发过程由哪些方法协作完成?
答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()
下文会对这3个方法进行详细介绍
1.5 总结
- Android事件分发机制的本质是要解决:点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。
这里的对象是指Activity、ViewGroup、View
- Android中事件分发顺序:Activity(Window) -> ViewGroup -> View
- 事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成
经过上述3个问题,相信大家已经对Android的事件分发有了感性的认知,接下来,我将详细介绍Android事件分发机制。
2. 事件分发机制方法&流程介绍
- 事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成,如下图:
- Android事件分发流程如下:(必须熟记)
Android事件分发顺序:Activity(Window) -> ViewGroup -> View
其中:
- super:调用父类方法
- true:消费事件,即事件不继续往下传递
- false:不消费事件,事件也不继续往下传递 / 交由给父控件onTouchEvent()处理
接下来,我将详细介绍这3个方法及相关流程。
2.1 dispatchTouchEvent()
属性 | 介绍 |
---|---|
使用对象 | Activity、ViewGroup、View |
作用 | 分发点击事件 |
调用时刻 | 当点击事件能够传递给当前View时,该方法就会被调用 |
返回结果 | 是否消费当前事件,详细情况如下: |
1. 默认情况:根据当前对象的不同而返回方法不同
对象 | 返回方法 | 备注 |
---|---|---|
Activity | super.dispatchTouchEvent() | 即调用父类ViewGroup的dispatchTouchEvent() |
ViewGroup | onIntercepTouchEvent() | 即调用自身的onIntercepTouchEvent() |
View | onTouchEvent() | 即调用自身的onTouchEvent() |
2. 返回true
- 消费事件
- 事件不会往下传递
- 后续事件(Move、Up)会继续分发到该View
- 流程图如下:
3. 返回false
- 不消费事件
- 事件不会往下传递
将事件回传给父控件的onTouchEvent()处理
Activity例外:返回false=消费事件
后续事件(Move、Up)会继续分发到该View(与onTouchEvent()区别)
-
- 流程图如下:
2.2 onTouchEvent()
属性 介绍 使用对象 Activity、ViewGroup、View 作用 处理点击事件 调用时刻 在dispatchTouchEvent()内部调用 返回结果 是否消费(处理)当前事件,详细情况如下: 与dispatchTouchEvent()类似
1. 返回true
- 自己处理(消费)该事情
- 事件停止传递
- 该事件序列的后续事件(Move、Up)让其处理;
- 流程图如下:
2. 返回false(同默认实现:调用父类onTouchEvent())
- 不处理(消费)该事件
- 事件往上传递给父控件的onTouchEvent()处理
- 当前View不再接受此事件列的其他事件(Move、Up);
- 流程图如下:
2.3 onInterceptTouchEvent()
属性 介绍 使用对象 ViewGroup(注:Activity、View都没该方法) 作用 拦截事件,即自己处理该事件 调用时刻 在ViewGroup的dispatchTouchEvent()内部调用 返回结果 是否拦截当前事件,详细情况如下: - 流程图如下:
2.4 三者关系
下面将用一段伪代码来阐述上述三个方法的关系和点击事件传递规则
// 点击事件产生后,会直接调用dispatchTouchEvent()方法 public boolean dispatchTouchEvent(MotionEvent ev) { //代表是否消耗事件 boolean consume = false; if (onInterceptTouchEvent(ev)) { //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件 //则该点击事件则会交给当前View进行处理 //即调用onTouchEvent ()方法去处理点击事件 consume = onTouchEvent (ev) ; } else { //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件 //则该点击事件则会继续传递给它的子元素 //子元素的dispatchTouchEvent()就会被调用,重复上述过程 //直到点击事件被最终处理为止 consume = child.dispatchTouchEvent (ev) ; } return consume; }
- 2.5 总结
- 对于事件分发的3个方法,你应该清楚了解
- 接下来,我将开始介绍Android事件分发的常见流程
3. 事件分发场景介绍
下面我将利用例子来说明常见的点击事件传递情况
3.1 背景描述
我们将要讨论的布局层次如下:
- 最外层:Activiy A,包含两个子View:ViewGroup B、View C
- 中间层:ViewGroup B,包含一个子View:View C
- 最内层:View C
假设用户首先触摸到屏幕上View C上的某个点(如图中黄色区域),那么Action_DOWN事件就在该点产生,然后用户移动手指并最后离开屏幕。
3.2 一般的事件传递情况
一般的事件传递场景有:
- 默认情况
- 处理事件
- 拦截DOWN事件
- 拦截后续事件(MOVE、UP)
3.2.1 默认情况
- 即不对控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())进行重写或更改返回值
- 那么调用的是这3个方法的默认实现:调用父类的方法
- 事件传递情况:(如图下所示)
- 从Activity A—->ViewGroup B—>View C,从上往下调用dispatchTouchEvent()
- 再由View C—>ViewGroup B —>Activity A,从下往上调用onTouchEvent()
- 流程图如下: