下面将以概念结合实例的方式,来讲解android中触摸事件的分发机制。
一,触摸事件传递的基本流程(默认情况)
通常,一个应用的一个页面是由Activity及view组成的,而view(通常是一个view树)则是由一系列layout对象和view对象组成的。一个事件传递的基本流程如下:
图1
从图中可以看出,事件总是先从Activity中的dispatchTouchEvent传递到view树中,自顶向下,依次传递到onInterceptTouchEvent(该方法表明此viewgroup是否拦截时间,默认情况下不拦截,所以继续向下传递),最后传递到叶节点——即view对象,于是执行该对象的onTouchEvent方法(该方法决定是否消费此事件,默认情况下,事件不消费,于是事件自底向上传递,如果没有view对象来处理此事件,则最后会一直传递到Activity中来)。
但在我们的应用开发中,肯定是希望对事件进行处理的,不然View就只是个摆设,而不能与用户交互了。举一个最简单的例子,我们在使用android中自带的Button的时候,Button默认是能消费该事件的,这是因为Button类中重写了onTouchEvent方法,在Button可点击的情况下,该方法的返回值是true。这里引申出了一个问题,即我们在想要使某个view对象能处理某个事件,是如何去做的呢?——即靠什么方式来改变上图中触摸事件默认传递的流程的呢?
答案是:通过集成ViewGroup/View来重写onInterceptTouchEvent或onTouchEvent, 并改变它的返回值(true or false)。Android 框架真是通过去判断具体的view对象的这些方法的返回值来决定事件的走向的。
二, 触摸事件传递的改变(自定义情况)
下面来以Button对象消费点击事件为例,给出事件传递的流程图,如下图所示。
图2
可以看到,事件默认传递到Button中时,Button处理了此事件(通过重写onTouchEvent并返回true),所以事件就不会像图1那样自底向上的传递回去,因为它已经被处理了(一次事件只会被处理一次)。
与重写View对象从而使得具体的View能处理事件类似,我们也通常会通过重写ViewGroup对象的方法来使得事件能按想要的方式传递,比如,一个ListView要处理滑动事件,那么它必须要拦截上下滑动事件(ListView通常是上下滑动的)并由自己来处理。那么是怎么做到的呢?事件如何拦截呢?拦截后又是如何处理它呢?
答案是:通过重写ViewGroup类的onInterceptTouchEvent,并判断是上下滑动的情况下,拦截事件(将返回值置为true);拦截之后事件就会交给onTouchEvent来处理,所以应该重写onTouchEvent并将返回值也置为true,然后进行滑动处理。下图给出了更改后事件的传递流程。
图3
从图中可以看到,ListView在拦截事件后就不会传递到它的子节点Button了。(但这不意味着Button永远不能接受到事件了,在一次事件完成后——即ACTION_UP之后,下一次的事件又会根据程序的处理来走一次新的流程。如,下一次执行Button上的点击,这个时候由于它不满足上下滑动,所以ListView是不会拦截此事件的,于是Button就能正常的接收到此事件了)。
三,结论
从以上几种实例,可以得出结论:
1. 事件的拦截是自顶向下——某节点拦截了事件后,就不会继续往下传递;
2. 事件的消费优先级是自底向上——最低端的节点最先决定时候消费该事件,如果是,则事件不会被其父节点处理。
3. 只有ViewGroup对象才能拦截事件,且一个事件被拦截后,将交由它的onTouchEvent方法来处理。
四,ViewPager+ListView实例
通过上述的实例讲解及结论,我们来以实现ViewPager+ListView的组合View(这里的情况是,ViewPager包含了ListView,并默认ViewPager是横向滑动的,ListView是纵向滑动的,在用户横着滑动时,事件交由ViewPager来处理,纵向滑动时,事件交由ListView来处理)
这里仍然以图示的方法来给出事件传递的流程。
图4
该图只是给处理基本的流程,具体的处理细节依据我们想要达到的效果而定,但基本的思路不变。有了以上的知识,就可以完成更复杂的自定义View事件处理机制。
五, 最后
该博文只是作为对触摸事件分发的浅析,当然有很多细节没有说到,如果想对其有更深入、更细致的了解,可以通过自己写Demo测试并结合Android源码的阅读来达到。