2.2.1 手势事件的分发流程

智能手机的一大革命性技术是把屏幕变为可触摸设备,既可用于信息输出(显示界面),又可用于信息输入(检测用户的触摸行为)。为方便开发者使用,Android已可自动识别特定的几种触摸手势,包括按钮的点击事件、长按事件、滚动视图的上下滚动事件、翻页视图的左右翻页事件等。不过对于App的高级开发来说,系统自带的几个固定手势显然无法满足丰富多变的业务需求。这就要求开发者深入了解触摸行为的流程与方法,并在合适的场合接管触摸行为,进行符合需求的事件处理。

与手势事件有关的方法主要有3个(按执行顺序排列),分别说明如下:

  • dispatchTouchEvent:进行事件分发处理,返回结果表示该事件是否需要分发。默认返回true表示分发给子视图,由子视图处理该手势,不过最终是否分发成功还得根据onInterceptTouchEvent方法的拦截判断结果;返回false表示不分发,此时必须实现自身的onTouchEvent方法,否则该手势将不会得到处理。
  • onInterceptTouchEvent:进行事件拦截处理,返回结果表示当前容器是否需要拦截该事件。返回true表示予以拦截,该手势不会分发给子视图,此时必须实现自身的onTouchEvent方法,否则该手势将不会得到处理;默认返回false表示不拦截,该手势会分发给子视图进行后续处理。
  • onTouchEvent:进行事件触摸处理,返回结果表示该事件是否处理完毕。返回true表示处理完毕,无须处理上一级视图的onTouchEvent方法,一路返回结束流程;返回false表示该手势事件尚未完成,返回继续处理上一级视图的onTouchEvent方法,然后根据上一级onTouchEvent方法的返回值判断直接结束或由上上一级处理。

上述手势方法的执行者有3个(按执行顺序排列),具体说明如下:

  • 页面类:包括Activity及其派生类。页面类可调用dispatchTouchEvent和onTouchEvent两个方法。
  • 容器类:包括从ViewGroup类派生出的各类容器,如各种布局Layout和ListView、GridView、Spinner、ViewPager、RecyclerView、Toolbar等。容器类可调用dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法。
  • 控件类:包括从View类派生的各类控件,如TextView、ImageView、Button等。控件类可调用dispatchTouchEvent和onTouchEvent两个方法。

只有容器类才能调用onInterceptTouchEvent方法,这是因为该方法用于拦截发往下层视图的事件,而控件类已经位于底层,只能被拦截,不能拦截别人。页面类没有下层视图,所以不能调用onInterceptTouchEvent方法。三类执行者的手势处理流程如图2-4所示。

图2-4 三类执行者的手势处理流程

以上流程图涉及3个手势方法和3种手势执行者,尤其是手势流程的排列组合千变万化,并不容易解释清楚。对于实际开发来说,真正需要处理的组合并不多,所以只要把常见的几种组合搞清楚就能应付大部分开发工作,这几种组合说明如下。

(1)页面类的手势处理。它的dispatchTouchEvent方法必须返回super.dispatchTouchEvent,如果不分发,页面上的视图就无法处理手势。至于页面类的onTouchEvent方法,基本没有什么作用,因为手势动作要由具体视图处理,页面直接处理手势没有什么意义。所以,页面类的手势处理可以不用关心,直接略过。

(2)控件类的手势处理。它的dispatchTouchEvent方法没有任何作用,因为控件下面没有子视图,无所谓分不分发。至于控件类的onTouchEvent方法,如果要进行手势处理,就需要自定义一个控件,重写自定义类中的onTouchEvent方法;如果不想自定义控件,就直接调用控件对象的setOnTouchListener方法,注册一个触摸监听器OnTouchListener,并实现该监听器的onTouch方法。所以,控件类的手势处理只需关心onTouchEvent方法。

(3)容器类的手势处理。这才是真正要深入了解的地方。容器类的dispatchTouchEvent与onInterceptTouchEvent方法都能决定是否将手势交给子视图处理。为了避免手势响应冲突,一般要重写dispatchTouchEvent或者onInterceptTouchEvent方法。两个方法的区别可以这么理解:前者是大领导,只管派发任务,不会自己做事情;后者是小领导,尽管有拦截的权利,不过也得自己做点事情,比如处理纠纷等。容器类的onTouchEvent方法近乎摆设,因为需要拦截的在前面已经拦截了,需要处理的在子视图已经处理了。

经过上面的详细分析,常见的手势处理方法有下面3种:

  • 页面类的dispatchTouchEvent方法:控制事件的分发,决定把手势交给谁处理。
  • 容器类的onInterceptTouchEvent方法:控制事件的拦截,决定是否要把手势交给子视图处理。
  • 控件类的onTouchEvent方法:进行手势事件的具体处理。

为方便理解dispatchTouchEvent方法,先看下面不派发事件的自定义布局代码:

(完整代码见event\src\main\java\com\example\event\widget\NotDispatchLayout.java)

活动页面实现的onNotDispatch方法代码如下:

(完整代码见event\src\main\java\com\example\event\EventDispatchActivity.java)

不派发事件的处理效果如图2-5和图2-6所示。图2-5的上面部分为正常布局,此时按钮可正常响应点击事件;图2-6的下面部分为不派发布局,此时按钮不会响应点击事件,取而代之的是执行不派发布局的onNotDispatch方法。

图2-5 正常布局允许分发事件

图2-6 不派发布局未分发事件

为方便理解onInterceptTouchEvent方法,再看拦截事件的自定义布局代码:

(完整代码见event\src\main\java\com\example\event\widget\InterceptLayout.java)

活动页面实现的onIntercept方法代码如下:

(完整代码见event\src\main\java\com\example\event\EventInterceptActivity.java)

拦截事件的处理效果如图2-7和图2-8所示。图2-7的上面部分为正常布局,此时按钮可正常响应点击事件;图2-8的下面部分为拦截布局,此时按钮不会响应点击事件,取而代之的是执行拦截布局的onIntercept方法。

图2-7 正常布局不拦截事件

图2-8 拦截布局会拦截事件