Android事件分发机制探究

了解Android中的事件分发机制是Android开发人员进阶的必要知识,网上其实也有很多的文章介绍,但要想好好理解它,除了要有一定的耐心外还要去亲自实践。同时这个事件分发机制的博文也不好写,要在短短的文字介绍中让读者理解,确实不容易,但为了加深理解于是就写了这篇文章。

首先我们介绍一下点击事件的分发,说白了就是对MotionEvent事件的分发过程。当MotionEvent产生后,系统需要把这个事件传递给一个具体的view,而这个过程就是所谓的分发过程。

在事件分发中,主角有两个,一个是ViewGroup,另一个则是View。

在ViewGroup中主要涉及三个函数:
dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()

而在View中则主要涉及两个函数:
dispatchTouchEvent()、onTouchEvent()

我们先来熟悉一下这三个函数的功能:
dispatchTouchEvent()是来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用。返回不同结果会有不同影响,后面再进行分析。

onInterceptTouchEvent()只有在ViewGroup中才有,用来判断是否要拦截某个事件。如果拦截,那么在同一个事件序列当中,此方法不会被再次调用(即下次默认直接调用dispatchTouchEvent()来进行事件的分发,不会再询问是否还要拦截事件),返回结果表示是否要拦截当前事件。

onTouchEvent()是在dispatchEvent()方法中调用的,表示用来处理点击事件,返回结果表示是消耗当前事件。如果不消耗,则在同一个事件序列中,当前的View无法再次接收到事件。

这里补充一下:同一事件序列是指在手指触摸手机屏幕到离开过程,产生的一系列的DOWN、MOVE、UP事件,这个序列事件以DOWN开始,中间包含不定的MOVE事件,最后以UP结束。

首先我们知道当一个事件产生后,它是根据Activity->Window->View的顺序来传递的。即事件总是先传给Activity,再由Activity传给Window,最后由Window传递给顶级的View。

Window会将事件传递给Decor View,而这个Decor View一般就是当前界面的底层容器。

首先当顶级的View获得事件后,这里的事件均是指ACTION_DWON。ACTION_MOVE,ACTION_UP与ACTION_DWON不同,后面会再进行分析。其实这里的ACTION_DWON就是确定在哪一个控件中消耗剩下的一系列事件用的,以便后面把事件传递过去。

现在我们先建立自定义的视图布局如下:
布局

然后在各个方法里打印日志。有了上面方法的分析后,再通过日志我们便可以知道事件分发的大致流程。

比如我们点击Button1,则最先调用Activity的dispatchTouchEvent(),然后通过ViewGroup1的dispatchTouchEvent(),如果ViewGroup1不拦截事件,即onInterceptTouchEvent()返回为false,(onInterceptTouchEvent()默认返回的也是false),就会调用
ViewGroup2的dispatchTouchEvent(),同理如果ViewGroup2也不拦截,就会调用Button1的dispatchTouchEvent(),然后把事件传递给onTouchEvent()去执行。

这个就是默认情况下事件分发的流向。

接下来我们来分析那三个重要函数的返回值,产生的影响。


在ViewGroup中的dispatchTouchEvent()方法里:
如果返回的是super.dispatchTouchEvent(event),则会对事件进行向下分发,比如上面的ViewGroup1传递给ViewGroup2一样;
如果返回的是true——表示ViewGroup的dispatTouchEvent()方法自己消费掉该事件,它也不会把事件传递给子视图(如ViewGroup2),也不会把事件传递给onTouchEvent()。并且后续的事件也只会传递给dispatchTouchEvent(),即事件在这里就终止了,不会向下分发;
如果返回的是false——表示事件会传递给它的上一级的onTouchEvent()方法,并且后续事件不会再传到该方法中,而是直接传递给它上一级的onTouchEvent()方法中。比如在ViewGroup2的dispatchTouchEvent()方法返回false,则就会调用ViewGroup1的onTouchEvent()方法,而在ViewGroup1返回false,就会调用Activity的onTouchEvent()方法。


而在View的dispatchTouchEvent()中基本一致,只是当返回super.dispatchTouchEvent(event)时,是直接传递给自己的onTouchEvent()方法,而不是传递给子视图(View也没有子视图)。


在ViewGroup中的onInterceptTouchEvent()方法里:
在前面dispatchTouchEvent()方法中,无论我们返回什么,ViewGroup的onTouchEvent()都没有执行。那是因为dispatchTouchEvent()要通过onInterceptTouchEvent()来实现事件传递给onTouchEvent()。
如果返回的是true——表示ViewGroup要对事件进行拦截,此时dispatchTouchEvent()方法便会把事件传递给ViewGroup的onTouchEvent(),并且下次不会再调用onInterceptTouchEvent()方法了,后续事件dispatchTouchEvent()会默认传给onTouchEvent()。
如果返回的是false——表示ViewGroup不会对事件进行拦截。默认返回的就是false,此时就可以向下进行事件分发。


在onTouchEvent()方法里:
如果返回的是true——表示控件(View或ViewGroup)要消耗该事件。事件传递到此为止,后续的事件也会陆续传递过来。
如果返回的是false——表示不消耗该事件。此时就会传递给上一级视图的onTouchEvent()。比如在Button1的onTouchEvent()返回false,默认就会调用ViewGroup2的onTouchEvent()方法。如果如果所有元素都没处理该事件,就会返回到Activity的onTouchEvent()方法中。

注意:View的onTouchEvent()默认都会消耗事件(返回true,即super.onTuchEvent()==true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认为false,click属性则分情况,Button为true,TextView为false。

通过上面的讲解,相信对事件分发有了一定的认识了。其实整个事件分发先从上到下,再从下到上执行。完成一个U型的流程。如下图:

流程图

以上便是针对ACTON_DOWN的事件分发流程。

关于ACTION_MOVE与ACTION_UP的事件分发

ACTION_DOWN与ACTION_MOVE、ACTION_UP的分发是不一样的,在上面的讲解中我们针对的ACTION_DOWN的情况。ACTION_DOWN就像先行者,找到我们要消费的具体控件,后面才把一系列的事件传递过去。

因此只要收到down事件的控件,才会获取到后面的move、up事件。简单的说,就是当dispatchTouchEvent()在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到ACTION_MOVE和ACTION_UP的事件。

接下来我们来验证一下:
黑色箭头表示:down事件传递方向
红色箭头表示:move、up事件传递方向
1、在ViewGroup1的dispatchTouchEvent()方法返回true,根据打印的日志我们可以画出down、move、up事件的流向。如下图所示:
demo3

2、在ViewGroup2的dispatchTouchEvent()方法返回true。如下图所示:
demo4

3、在View的dispatchTouchEvent()方法返回true。(这里我们选择Button1中的方法返回)。如下图所示:
demo5

通过上面的分析我们可以得出结论:
如果在某个控件的dispatchEvent中返回true消费终结事件,那么收到ACTION_DWON的函数也能收到ACTION_MOVE、ACTION_UP。

4、在ViewGroup1的onTouchEvent()方法中返回true,在Button1的onTouchEvent()返回false。我们观察结果图如下:
demo6

可以看到move、up事件是直接传递到down事件到达的控件的onTouchEvent()方法中的。

比如我们在ViewGroup2的onTouchEvent()方法中返回true,同样的在Button1的onTouchEvent()返回false。结果如下图:
demo7

我们可以得到结论:
ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent())做事件分发往下传,就只会传到这个控件,不会继续往下传。就像前面提到的一样,down事件像一个先行的开路者,到达后再把目的地告诉后面的事件。

5、在ViewGroup1的onTouchEvent()方法中返回true,同时在Button1中的dispatchTouchEvent()中返回false。结果如下图:
demo8

跟前面提到的分析结果一致,同时我们可以看到在dispatchTouchEvent()返回false,会调用它上一级的onTouchEvent()方法,从而跳过自己的方法。

6、在ViewGroup1的onTouchEvent()方法中返回true,同时在ViewGroup2的onInterceptTouchEvent()方法中返回ture。图如下所示:
demo9

讲了这么多,最后也画了很多的图,我想大致的Anroid事件的大致流程应该讲解清楚了。欢迎,留言!

相关内容推荐