@lovesosoi
2018-05-29T07:11:46.000000Z
字数 6253
阅读 605
Android32_事件的分发和消费机制
Android面试
Activity
或View
类的onTouchEvent()
回调函数会接收到touch
事件。一个完整的手势是从ACTION_DOWN
开始,到ACTION_UP
结束。
简单的情况下,我们只需要在onTouchEvent()
中写个switch case
语句,处理各种事件(Touch Down、 Touch Move、 Touch Up
等),但是比较复杂的动作就需要更多的处理了。
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)
。
dispatchTouchEvent() | 用来分派事件。 其中调用了onInterceptTouchEvent()和onTouchEvent(),一般不重写该方法 |
---|---|
onInterceptTouchEvent() | 用来拦截事件。 ViewGroup类中的源码实现就是{return false;}表示不拦截该事件, 事件将向下传递(传递给其子View);若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递, 事件由当前ViewGroup类来处理,就是调用该类的onTouchEvent()方法 |
onTouchEvent() | 用来处理事件。返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View); 返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理 |
Touch
事件发生时Activity
的 dispatchTouchEvent(MotionEvent ev)
方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View
的dispatchTouchEvent(MotionEvent ev)
方法,并由该 View
的 dispatchTouchEvent(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
进行处理。
在 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(拦截)
。
正确的使用方法是,在此方法内仅判断事件是否需要拦截,然后返回。即便需要拦截也应该直接返回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)
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
的话,这个事件就会“消失”,而且接收不到下一次事件。
【以下动作均为点击自定义View】
03-31 18:50:26.344: I/MainActivity(15304): --->dispatchTouchEvent: 0
03-31 18:50:26.344: I/MyViewGroup(15304): --->dispatchTouchEvent: 0
03-31 18:50:26.372: I/MyViewGroup(15304): --->onInterceptTouchEvent: 0
03-31 18:50:26.392: I/MyViewGroup(15304): --->onTouchEvent: 0
03-31 18:50:26.402: I/MainActivity(15304): --->onTouchEvent: 0
03-31 18:50:26.526: I/MainActivity(15304): --->dispatchTouchEvent: 1
03-31 18:50:26.526: I/MainActivity(15304): --->onTouchEvent: 1
A:dispatch:0
G:dispatch:0
G:intercept:0
Gi:dispatch:0
Gi:intercepte:0
V:dispatch:0
V:touch:0
Gi:touch:0
A:dispatch:1
G:disaptch:1
G:intercept:1
Gi:dispatch:1
Gi:touch:1
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
。
【备注:】
如果一个View是Clickable或者longClickable, onTouchEvent()就直接返回true, 表示该View就一直消费Touch事件。也就是说,一个clickable或者longclickable的View是一直消费Touch事件的,而一般的View既不是clickable也不是longclickable的(即不会消费Touch事件,只会执行ACTION_DOWN而不会执行ACTION_MOVE和ACTION_UP) 。
Button是clickable的,可以消费Touch事件,但是我们可以通过setClickable()和setLongClickable()来设置View是否为clickable和longClickable。当然还可以通过重写View的onTouchEvent()方法来控制Touch事件的消费与否。