View 的事件分发机制和触摸反馈机制原理解析
在学习和实践中对 View 事件分发的一些简单认识和总结,今天整理一下…
事件分发机制的过程就是一个 MotionEvent 产生以后,系统需要把这个事件传递给具体的view,这个过程就是事件分发过程。
View 的事件分发机制
MotionEvent 事件类型
事件类型 MotionEvent 的一个重要概念,就是指 MotionEvent 对象所代表的动作。比如说,当你的一个手指在屏幕上滑动一下时,系统会产生一系列的触摸事件对象,他们所代表的动作有所不同。有的事件代表你手指按下这个动作,有的事件代表你手指在屏幕上滑动,还有的事件代表你手指离开屏幕。这些事件的事件类型就分别为ACTION_DOWN,ACTION_MOVE,和ACTION_UP。上述这些动作所产生的一系列事件,被称为一个事件流,它包括一个ACTION_DOWN 事件,很多个 ACTION_MOVE 事件,和一个 ACTION_UP 事件。
除了前面说的三个事件类型外,还有很多不同的事件类型,比如 ACTION_CANCEL。它代表当前的手势被取消。要理解这个类型,就必须要了解ViewGroup分发事件的机制。一般来说,如果一个子视图接收了父视图分发给它的ACTION_DOWN事件,那么与ACTION_DOWN事件相关的事件流就都要分发给这个子视图,但是如果父视图希望拦截其中的一些事件,不再继续转发事件给这个子视图的话,那么就需要给子视图一个ACTION_CANCEL事件。
常用的事件类型和含义
MotionEvent 记录手指接触屏幕以后所产生一系列的事件,也就是事件分发的对象,事件的类型与含义如下:
事件类型 | 具体动作 |
---|---|
MotionEvent.ACTION_DOWN | 按下 View (所有事件的开始) |
MotionEvent.ACTION_MOVE | 滑动 View |
MotionEvent.ACTION_UP | 抬起 View (与 DOWN 对应) |
MotionEvent.ACTION_CANCEL | 取消或结束事件 |
MotionEvent.ACTION_POINTER_DOWN | 代表用户又使用一个手指触摸到屏幕上,也就是说,在已经有一个触摸点的情况下,又新出现了一个触摸点 |
MotionEvent.ACTION_POINTER_UP | 代表用户的一个手指离开了触摸屏,但是还有其他手指还在触摸屏上。也就是说,在多个触摸点存在的情况下,其中一个触摸点消失了。它与ACTION_UP的区别就是,它是在多个触摸点中的一个触摸点消失时(此时,还有触摸点存在,也就是说用户还有手指触摸屏幕)产生,而ACTION_UP可以说是最后一个触摸点消失时产生. |
事件分发的顺序 (Activity -> Window -> View)
事件总是先传递给 Activity,Activity 再传递给 Window,最后 Window 再传递给顶级 View ( DecorView 本质是 ViewGroup,DecorView一般就是当前界面的底层容器(即setContentView所设置的View的父容器),顶级 View 接收到事件后,就会按照事件分发机制去分发事件。
如果一个View的 onTouchEvent 返回 false,那么它的父容器的 onTouchEvent 将会被调用,依次类推,如果所有的元素都不处理这个事件,那么这个事件最终将会传递给 Activity 处理。
事件分发核心方法
方法名 | 作用 | 备注 |
---|---|---|
dispatchTouchEvent(MotionEvent event) | 进行事件分发 | 是第一个被调用的方法,若事件能够传递给当前的 View/ViewGroup,那么这个方法就一定会被调用 |
onInterceptTouchEvent(MotionEvent event) | 进行事件拦截操作(ViewGroup 有此方法,View 没有) | 在 dispatchTouchEvent() 方法中调用,如果当前 view 拦截了某一个事件,那么同一个事件序列将不会再调用这个方法 |
onTouchEvent(MotionEvent event) | 进行处理上面提到的事件流 | onTouchEvent 是在 dispatchTouchEvent() 方法中被调用的, |
事件分发图示(父 View/ ViewGroup 到子 View 的事件分发过程)
如下图:
简单来说,view 的 dispatchTouchEvent 自身做的事情并不多,主要是 onTouchEvent();总结一下就是 父 view 递归的调用 子 view 的 dispatchTouchEvent 的过程。
伪代码演示下他们的调用关系:
1 | View.dispatchTouchEvent(); |
把 result 结果记录,然后返回(下次在执行时候直接会根据上次记录的 result 进行操作,同一个事件序列将不会再调用)
通过上面的伪代码,可以大致了解点击事件的传递规则:
对于一个根 ViewGroup 来说,点击事件产生后,首先会传递给它,这是它的 dispatchTouchEvent 就会被调用,如果这个 ViewGroup 的 onInterceptTouchEvent 方法返回 true 就表示它要拦截当前事件,接着事件就会交给这个 ViewGroup 处理,这时如果它的 mOnTouchListener 被设置,则 onTouch 会被调用,否则 onTouchEvent 会被调用。
在 onTouchEvent 中,如果 设置了 mOnCLickListener,则 onClick 会被调用。
只要 View 的 CLICKABLE 和 LONG_CLICKABLE 有一个为 true,onTouchEvent() 就会返回 true 消耗这个事件。
如果这个 ViewGroup 的 onInterceptTouchEvent 方法返回 false 就表示它不拦截 当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的 dispatchTouchEvent 方法就会被调用,如此反复直到事件被最终处理完。
未完待续…