Android事件拦截机制详解

当Android系统捕获到用户的触摸事件后,我们该如何准确地找到真正想要处理这个事件的控件呢?对于一些常见的滑动冲突,我们又该如何去解决呢?看完这一篇,你就全明白了。

触摸事件

要想了解触摸事件的拦截机制,首先我们必须得清楚什么是触摸事件。很简单,触摸事件就是捕获到用户触摸屏幕的行为后产生的事件。

比如说,当一个按钮被点击时,通常就会产生两至三个事件:按下,(滑动),抬起。Android专门为触摸事件封装了一个类:MontionEvent。如果要重写触摸相关的方法,比如onTouchEvent(), 参数一般都含有MontionEvent类。

在MontionEvent类里,封装了不少有用的东西。比如通过event.getX()以及evnet.getRawX()方法来分别获取触摸点的相对位置和绝对位置;还有通过event.getAction()获取触摸类型(MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP等)。

所以说,触摸事件就是一个简单的动作类型加坐标。但Android的View结构是树状的,可以通过不同的组合来实现不同样式。举一个简单的例子,有一个View,放在一个ViewGroupA里,而这个ViewGroupA放在一个ViewGroupB里,此时触摸这个View,这个事件到底该怎么分呢?所以,就产生了事件拦截这个东西。

事件拦截

构建模型

事件拦截在身边可以找到一个非常契合且非常贴近生活的例子。比如在你的公司,你是一个普通员工,在你的上面是技术主管,技术主管再往上是总经理。总经理给技术主管安排任务,技术主管再分派给你;你完成任务后,汇报给技术主管,技术主管再汇报给总经理。

在上面的例子里,总经理就是最外层的ViewGroupA,技术主管就是中间的ViewGroupB,你就是最里面的那个View

事件拦截模型

我们这里创建对应的布局文件,并重写控件里的方法,来加深我们对事件拦截机制的理解。

对于ViewGroupA,重写如下三个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("Chen", "ViewGroupA dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("Chen", "ViewGroupA onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("Chen", "ViewGroupA onTouchEvent");
return super.onTouchEvent(event);
}

对于View,重写如下两个方法。

1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("Chen", "View dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("Chen", "View onTouchEvent");
return super.onTouchEvent(event);
}

正常情形

这里可以看出,ViewGroup的级别比较高,比View多出一个onInterceptTouchEvent()方法。这个名字很容易地让我们猜到是事件拦截地核心方法。我们先不改动任何返回值,先点击一下View,观察Log:

D/Chen: ViewGroupA dispatchTouchEvent
D/Chen: ViewGroupA onInterceptTouchEvent
D/Chen: ViewGroupB dispatchTouchEvent
D/Chen: ViewGroupB onInterceptTouchEvent
D/Chen: View dispatchTouchEvent
D/Chen: View onTouchEvent
D/Chen: ViewGroupB onTouchEvent
D/Chen: ViewGroupA onTouchEvent

所以我们发现,在正常情形下,事件的传递顺序是:总经理(ViewGroupA) –> 技术主管(ViewGroupB) –> 你(View)。事件传递时先执行dispathTouchEvent()方法(View没有该方法),再执行onInterceptTouchEvent()方法。 事件的处理顺序是你(View) –> 技术主管(ViewGroupB) –> 总经理(ViewGroupA)。事件处理时执行的是onTouchEvent()方法。

情形一

事件分发拦截

事件传递以及处理的返回值非常容易理解,True就是拦截或处理了,不再继续传递;False就是继续传递。初始情况下,返回值都是False。接下来我们就修改下返回值,验证我们的上述说法。通常情况下,我们不太会改动dispathTouchEvent()方法,而是用onInterceptTouchEvent()进行事件拦截。

假设总经理(ViewGroupA)发现任务非常简单,自己举手之劳就能完成,不需要再找手下人来做了,于是他就决定自己来做。我们修改ViewGroupA的onInterceptTouchEvent()方法,使其返回Ture,接下来观察Log:

D/Chen: ViewGroupA dispatchTouchEvent
D/Chen: ViewGroupA onInterceptTouchEvent
D/Chen: ViewGroupA onTouchEvent

跟设想的一样,总经理拦截了事件,手下人就不需要再做这件事了。

情形二

同理,如果总经理照常分发给技术主管,但技术主管拦截了事件,即ViewGroupB的onInterceptTouchEvent()方法返回了True,Log就会变成这样:

D/Chen: ViewGroupA dispatchTouchEvent
D/Chen: ViewGroupA onInterceptTouchEvent
D/Chen: ViewGroupB dispatchTouchEvent
D/Chen: ViewGroupB onInterceptTouchEvent
D/Chen: ViewGroupB onTouchEvent
D/Chen: ViewGroupA onTouchEvent

可以看到,这次不用干活的仅仅就是你(View)了。

情形三

事件处理

说完了事件拦截分发,接下就是事件的处理了。在上面的正常情形中,我们可以看到,你(View)完成了工作,需要向技术主管(ViewGroupB)汇报,接着技术主管需要向总经理(ViewGroupA)汇报。

假如你(View)觉得这个工作完成后,没必要再向技术主管(ViewGroupA)汇报了,即在View的onTouchEvnet()方法里返回了True,我们来看一下Log:

D/Chen: ViewGroupA dispatchTouchEvent
D/Chen: ViewGroupA onInterceptTouchEvent
D/Chen: ViewGroupB dispatchTouchEvent
D/Chen: ViewGroupB onInterceptTouchEvent
D/Chen: View dispatchTouchEvent
D/Chen: View onTouchEvent

可以发现,事件的传递跟正常情形还是一样,但是事件的处理,到了你(View)这里就结束了,因为你没有继续往上进行汇报。

情形四

如果你(View)正常向技术主管(ViewGroupB)汇报了,但是技术主管觉得没必要再向总经理(ViewGroupA)进行汇报了,Log就会变成这样:

D/Chen: ViewGroupA dispatchTouchEvent
D/Chen: ViewGroupA onInterceptTouchEvent
D/Chen: ViewGroupB dispatchTouchEvent
D/Chen: ViewGroupB onInterceptTouchEvent
D/Chen: View dispatchTouchEvent
D/Chen: View onTouchEvent
D/Chen: ViewGroupB onTouchEvent

这一次,事件分发到了技术主管(ViewGroupB)这里就结束了。

情形五

总结

通过上面这么接地气的实例,相信你一定对事件的拦截分发以及处理的流程有了一个非常深入的了解了。

虽然这个模型是一个最简单的模型,但只要你真正理解了,就能够举一反三。以后再遇到任何复杂的需求,比如滑动冲突的解决,相必你都可以想到方法去解决~

Chen wechat
欢迎扫描二维码,订阅我的博客公众号MiracleChen