一、简介 :
事件分发主要分为两部分:view的事件分发和viewgroup的事件分发。在探讨事件分发机制之前,先需要搞清楚android两个基础控件view和viewgroup,以及它们之间的关系:view是没有子控件的,像button,textview都是view控件。而viewgroup继承自view,是可以存在子控件的。也就是说viewgroup就是一组view或者是viewroup的集合,它是所有页面布局的父类(eg linearlayout,relativelayout)
Activity或View类的onTouchEvent()回调函数会接收到touch事件。
一个完整的手势是从ACTION_DOWN开始,到ACTION_UP结束。
简单的情况下,我们只需要在onTouchEvent()中写个switch case语句,处理各种事件(Touch Down、 Touch Move、 Touch Up、Touch Cancle等),但是比较复杂的动作就需要更多的处理了。
ViewGroup作为一个parent(父容器)是可以截获传向它的child的touch事件的。
如果一个ViewGroup的onInterceptTouchEvent()方法返回true,说明Touch事件被截获,子View不再接收到Touch事件,而是转向本ViewGroup的 onTouchEvent()方法处理。从Down开始,之后的Move,Up都会直接在onTouchEvent()方法中处理。
先前还在处理touch event的child view将会接收到一个 ACTION_CANCEL。
如果onInterceptTouchEvent()返回false,则事件会交给child view处理。
Android中提供了ViewGroup、View、Activity三个层次的Touch事件处理。
处理过程是按照Touch事件从上到下传递,再按照是否消费的返回值,从下到上返回,即
如果View的onTouchEvent返回false,将会向上传给它的parent的ViewGroup,如果ViewGroup不处理,将会一直向上返回到Activity。
即
隧道式向下分发,然后冒泡式向上处理。
Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能够响应这些方法的控件包括:ViewGroup、View、Activity。方法与控件的对应关系如下表所示:
Touch 事件相关方法 | 方法功能
| ViewGroup
| View
| Activity
|
public boolean dispatchTouchEvent(MotionEvent ev) | 事件分发
| Yes | Yes | Yes |
public boolean onInterceptTouchEvent(MotionEvent ev)
| 事件拦截
| Yes | Yes / No | No |
public boolean onTouchEvent(MotionEvent ev) | 事件响应
| Yes | Yes | Yes |
从这张表中我们可以看到 ViewGroup 和 View 对与 Touch 事件相关的三个方法均能响应,而 Activity 对 onInterceptTouchEvent(MotionEvent ev) 也就是事件拦截不进行响应。另外需要注意的是 View 对 onInterceptTouchEvent(MotionEvent ev) 的响应的前提是可以向该 View 中添加子 View,如果当前的 View 已经是一个最小的单元 View(比如 TextView),那么就无法向这个最小 View 中添加子 View,也就无法向子 View 进行事件的拦截,所以它没有 onInterceptTouchEvent(MotionEvent ev)。
一、Touch 事件分析
(一)、
事件分发:public boolean
dispatchTouchEvent(MotionEvent ev)
Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以
隧道方式(
从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递即被拦截)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。
(二)、
事件拦截:public boolean
onInterceptTouchEvent(MotionEvent ev)
在外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件拦截逻辑如下:
- •如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
- •如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
- •如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默认不会被拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理。
(三)、
事件响应:public boolean
onTouchEvent(MotionEvent ev)
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:
- •如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
- •如果返回了 true 则会接收并消费该事件。
- •如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
onInterceptTouchEvent用于改变事件的传递方向。决定传递方向的是返回值,返回为false时事件会传递给子控件,返回值为true时事件会传递给当前控件的onTouchEvent(),这就是所谓的Intercept(拦截)。
[tisa ps:
正确的使用方法是,在此方法内仅判断事件是否需要拦截,然后返回。即便需要拦截也应该直接返回true,然后由onTouchEvent方法进行处理。]
onTouchEvent用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。尤其对于ACTION_DOWN事件,返回true,表示我想要处理后续事件;返回false,表示不关心此事件,并返回由父类进行处理。
可能你要问是否消费了又区别吗,反正我已经针对事件编写了处理代码?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。
在没有重写onInterceptTouchEvent()和onTouchEvent()的情况下(他们的返回值都是false)
Android系统中的每个View的子类都具有下面三个和TouchEvent处理密切相关的方法:
1)public boolean
dispatchTouchEvent(MotionEvent ev) 这个方法用来
分发TouchEvent
2)public boolean
onInterceptTouchEvent(MotionEvent ev) 这个方法用来
拦截TouchEvent
3)public boolean
onTouchEvent(MotionEvent ev) 这个方法用来
处理TouchEvent
1、如果dispatchTouchEvent返回true ,则交给这个view的onTouchEvent处理, 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
2、如果dispatchTouchEvent返回 false ,则交给这个 view的interceptTouchEvent方法来决定是否
要拦截这个事件,如果 interceptTouchEvent 返回 true ,表示拦截掉了,则交给它的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么就传递给子view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。
3、如果事件传递到某一层的子 view 的onTouchEvent 上了,这个方法返回了 false ,那么这个事件
会从这个view 往上传递,都是 onTouchEvent 来接收。如果传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“消失”,而且接收不到下一次事件。
1、核心代码:
【以下动作均为点击自定义View】
一、无拦截,无消费:9个
二、ViewGroup拦截,无消费:7个
三、无拦截,View消费:10个
四、无拦截,ViewGroup消费:9个
五、无拦截,Activity消费:9个
六、无拦截、无消费,仅在ViewGroup上设置单击监听,执行单击动作后:10个
七、无拦截、无消费,仅在ViewGroup上设置长按监听和单击监听,执行长按动作后:(如果长按返回false:11个 , 如果长按返回true:10个)
八、无拦截、无消费,在View上设置单击监听,执行单击动作后:11个
九、无拦截、无消费,在View上设置长按监听和单击监听,执行长按动作后:(如果长按返回false:12个 , 如果长按返回true:11个)
Click事件处理
Click事件:View的短按和长按都是注册监听器的(setListener):
onClick是在ACTION_UP之后执行的。
onLongClick则是按下到一定时间之后执行的,这个时间是ViewConfiguration中的:
private static final int TAP_TIMEOUT = 180; //180毫秒
这里需要注意onLongClick的返回值,如果是false,则onLongClick之后,手指抬起,ACTION_UP之后还是回执行到onClick;但是如果onLongClick返回true,则不会再调用onClick。
良心的公众号,更多精品文章,不要忘记关注哈
《Android和Java技术栈》
