摘要
自定义控件在工作中时常有所接触,但始终是只得其形,不得其神。最近抽空系统学习了下这方面的知识,准备用“Android自定义控件”作为一个博客系列来记录我的学习心得。系列文中不乏引用到Android SDK源码,以API 25为准。最后,如有雷同,没错,就是我去抄的。
Android自定义控件系列目录
Android自定义控件 —— 事件分发
Android自定义控件 —— 三大流程
基础认知
当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象,即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的View去处理,这个事件传递的过程就是分发过程。
为什么要有事件分发机制?安卓上面的View是树形结构的,View可能会重叠在一起,当我们点击的地方有多个View都可以响应的时候,这个点击事件应该给谁呢?为了解决这一个问题,就有了事件分发机制。
View是树形结构的,基于这样的结构,我们的事件可以进行有序的分发。事件收集之后最先传递给Activity, 然后依次向下传递,大致如下
Activity -> PhoneWindow -> DecorView -> ViewGroup -> … -> View
这样的事件分发机制逻辑非常清晰,可是,如果最后分发到View,而这个View也没有处理事件怎么办,就这样让事件浪费掉?
当然不会,如果没有任何View消费掉事件,那么这个事件会按照反方向回传,最终传回给Activity,如果最后Activity也没有处理,本次事件才会被抛弃
Activity <- PhoneWindow <- DecorView <- ViewGroup <- … <- View
由于PhoneWindow、DecorView和RootView并不是我们需要关心的,所以我们只需要理清Activity、ViewGroup和View之间的事件分发即可。
参与事件分发过程的方法如下
public boolean dispatchTouchEvent(MotionEvent ev)
public boolean onInterceptTouchEvent(MotionEvent ev)
public boolean onTouchEvent(MotionEvent event)
下面用一张图来做个概述
方法 | 作用 | 调用 | Activity | ViewGroup | View |
---|---|---|---|---|---|
dispatchTouchEvent | 分发(传递)点击事件 | 当点击事件能够传递到当前View时,该方法被调用 | √ | √ | √ |
onInterceptTouchEvent | 判断是否拦截了某个事件 | 在dispatchTouchEvent内部调用 | X | √ | X |
onTouchEvent | 处理点击事件 | 在dispatchTouchEvent内部调用 | √ | √ | √ |
其中,√ 表示有该方法,X 表示没有该方法。
关于和事件分发相关的三个函数的返回值,分为
super:调用父类方法
true:消费事件,即事件不继续往下传递
false:不消费事件,事件也不继续往下传递 / 交由给父控件onTouchEvent处理
事件分发流
下面用一张图来说明当点击屏幕进行事件分发时的流程图(即ACTION_DOWN事件)
如果事件不被中断,整个事件流向是一个类U型图,我们来看下这张图,可能更能理解U型图的意思。
所以如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是 Activity -> ViewGroup -> View 从上往下调用dispatchTouchEvent(MotionEvent)
方法,一直到叶子节点(View)的时候,再由 View -> ViewGroup -> Activity 从下往上调用onTouchEvent(MotionEvent)
方法。
dispatchTouchEvent(MotionEvent)
和onTouchEvent(MotionEvent)
一旦return true
,事件就停止传递了(到达终点,没有谁能再收到这个事件)。看下图中只要return true
事件就没再继续传下去了,对于return true
我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。
dispatchTouchEvent(MotionEvent)
和onTouchEvent(MotionEvent)
若return false
,则事件都回传给父控件的onTouchEvent(MotionEvent)
处理。
ViewGroup和View对事件的默认实现就是会让整个事件按照U型完整走完,所以return super.xxxxxx()
就会让事件依照U型的方向的完整走完整个事件流动路径,中间不做任何改动,不回溯、不终止,每个环节都走到。
下面就来说明一下onInterceptTouchEvent(MotionEvent)
的作用
Intercept的意思就拦截,每个ViewGroup每次在做分发的时候,都要问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理)。如果要自己处理,那就在onInterceptTouchEvent(MotionEvent)
方法中return true
,这样一来就会交给自己的onTouchEvent(MotionEvent)
来进行处理;如果不拦截就继续往子控件往下传。
拦截器的默认实现是不会去拦截的,因为子View也需要这个事件。所以onInterceptTouchEvent(MotionEvent)
拦截器的默认返回(即return super.onInterceptTouchEvent()
)和return false
是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent(MotionEvent)
传递。
上面讲解的都是针对ACTION_DOWN的事件传递,ACTION_MOVE和ACTION_UP在传递的过程中并不是和ACTION_DOWN一样,当在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。
简单的说,就是当dispatchTouchEvent(MotionEvent)
在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到ACTION_MOVE和ACTION_UP的事件。
不过可以肯定的是,ACTION_MOVE和ACTION_UP肯定在获取到消费权的函数中可以收到。
参考链接:
1. 图解 Android 事件分发机制
2. 安卓自定义View进阶-事件分发机制原理
3. Android事件分发机制详解:史上最全面、最易懂