Android 自定义View:处理事件分发(四)

我看过国内讲解Android事件分发的大多数文章,但遗憾的是都没有这篇讲的好,缘由有二:它阐明了具体的事件分发机制的设计意图,让人既知其然,又知其因此然;它没有贴源码,吓唬本宝宝。因此我决定将它翻译出来,造福广大Android开发者。原文请点击这里android

有时,你必需要本身处理触摸事件(touch events)而不能依赖于有可用的onSomethingListener。我就遇到过这样的时候,当时我很想有一篇文章能简单地解释触摸事件是怎样在视图层次(view hierarchy)中传播的,从而能够将之做为进一步深刻学习的起点。这篇博客是个人一次尝试,它看起来有点长,但这是由于我是按照触摸事件的传播过程一步一步来写的。post

一些假设

咱们只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL。一个手势(gesture)是一个事件列,以一个DOWN事件开始(当用户触摸屏幕时产生),后跟0个或多个MOVE事件(当用户四处移动手指时产生),最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)因为其余缘由结束时产生)。当咱们说到“手势剩余部分”时指的是手势后续的MOVE事件和最后的UP或CANCEL事件。学习

在这里我也不考虑多点触摸手势(咱们只假设用一个手指)而且忽略多个MOVE事件能够被归为一组这一实际状况。最后,咱们假设文中的view都没有注册onTouchListener。动画

咱们将要讨论的视图层次是这样的:最外层是一个ViewGroup A,包含一个或多个子view(children),其中一个子view是ViewGroup B,ViewGroupB中又包含一个或多个子view,其中一个子view是 View C,C不是一个ViewGroup。这里咱们忽略同层级view之间可能的交叉叠加。spa

假设用户首先触摸到的屏幕上的点是C上的某个点,该点被标记为触摸点(touch point),DOWN事件就在该点产生。而后用户移动手指并最后离开屏幕,此过程当中手指是否离开C的区域可有可无,关键是手势(gesture)是从哪里开始的。翻译

默认状况

假设上面的A,B,C都没有覆写默认的事件传播行为,那么下面就是事件传播的过程:设计

  • DOWN事件被传到C的onTouchEvent方法中,该方法返回false,表示“我不关心这个手势(gesture)”。
  • 所以,DOWN事件被传到B的onTouchEvent方法中,该方法一样返回false,表示B也不关心这个手势。
  • 一样,由于B不关心这个手势,DOWN事件被传到A的onTouchEvent方法中,该方法也返回false。

因为没有view关心这个手势(gesture),它们将再也不会从“手势剩余部分”中接收任何事件。3d

处理事件

如今,让咱们假设C其实是关心这个手势(gesture)的,缘由多是C被设置成可点击的(clickable)或者你覆写了C的onTouchEvent方法。cdn

  • DOWN事件被传递给C的onTouchEvent方法,该方法能够作任何它想作的事情,最后返回true。
  • 由于C说它正在处理这个手势(gesture),则DOWN事件将再也不被传递给B和A的onTouchEvent方法。
  • 由于C说它正在处理这个手势(gesture),因此“手势剩余部分”的事件也将传递给C的onTouchEvent方法,此时该方法返回true或false都可有可无了,可是为保持一致最好仍是返回true。

我的理解:从这里能够看出,各个View的onTouchEvent方法对DOWN事件的处理,表明了该View对以此DOWN开始的整个手势(gesture)的处理意愿,返回true表明愿意处理该gesture,返回false表明不肯意处理该gesture。blog

onInterceptTouchEvent

如今咱们将讨论一个新的方法:onInterceptTouchEvent,它只存在于ViewGroup中,普通的View中没有这个方法。在任何一个view的onTouchEvent被调用以前,它的父辈们(ancestors)将先得到拦截这个事件的一次机会,换句话说,它们能够窃取该事件。在刚才的“处理事件”部分中,咱们遗漏了这一过程,如今,让咱们把它加上:

  • DOWN事件被传给A的onInterceptTouchEvent,该方法返回false,表示它不想拦截。
  • DOWN又被传递给B的onInterceptTouchEvent,它也不想拦截,所以该方法也返回false。
  • 如今,DOWN事件被传递到C的onTouchEvent方法,该方法返回true,由于它想处理以该事件为首的手势(gesture)。
  • 如今,该手势的下一个事件MOVE到来了。这个MOVE事件再一次被传递给A的onInterceptTouchEvent方法,该方法再一次返回false,B也一样如此。
  • 而后,MOVE事件被传递给C的onTouchEvent,就像在前一部分中同样。
  • “手势剩余部分”中其余事件的处理过程和上面同样,假如A和B的onInterceptTouchEvent方法继续返回false的话。

这里有两点须要注意:

  • 虽然ViewGroup A和B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件依然会传递给它们的onInterceptTouchEvent方法,这一点与onTouchEvent的行为是不同的。
  • 假如DOWN事件传给C的onTouchEvent方法时,它返回了false,DOWN事件会继续向上传递给B和A的onTouchEvent,即便它们在onInterceptTouchEvent方法中说它们不想拦截这个DOWN事件,但没办法,没有子View愿意处理该事件。

我的理解:因而可知,DOWN事件的处理实际上经历了一下一上两个过程,下是指A->B的onInterceptTouchEvent,上是指C->B->A的onTouchEvent,固然,任意一步的方法中返回true,都能阻止它继续传播。

拦截事件

如今,让咱们更进一步,假设B没有拦截DOWN事件,但它拦截了接下来的MOVE事件。缘由多是B是一个scrolling view。当用户仅仅在它的区域内点击(tap)时,被点击到的元素应当能处理该点击事件。可是当用户手指移动了必定的距离后,就不能再视该手势(gesture)为点击了——很明显,用户是想scroll。这就是为何B要接管该手势(gesture)。

下面是事件被处理的顺序:

  • DOWN事件被依次传到A和B的onInterceptTouchEvent方法中,它们都返回的false,由于它们目前还不想拦截。
  • DOWN事件传递到C的onTouchEvent方法,返回了true。
  • 在后续到来MOVE事件时,A的onInterceptTouchEvent方法仍然返回false。
  • B的onInterceptTouchEvent方法收到了该MOVE事件,此时B注意到用户手指移动距离已经超过了必定的threshold(或者称为slop)。所以,B的onInterceptTouchEvent方法决定返回true,从而接管该手势(gesture)后续的处理。
  • 而后,这个MOVE事件将会被系统变成一个CANCEL事件,这个CANCEL事件将会传递给C的onTouchEvent方法。
  • 如今,又来了一个MOVE事件,它被传递给A的onInterceptTouchEvent方法,A仍是不关心该事件,所以onInterceptTouchEvent方法继续返回false。
  • 此时,该MOVE事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就不再会被调用了。事实上,该MOVE以及“手势剩余部分”都将传递给B的onTouchEvent方法(除非A决定拦截“手势剩余部分”)。
  • C不再会收到该手势(gesture)产生的任何事件了。

下面的一些小事情可能会令你感到吃惊:

  • 若是一个ViewGroup拦截了最初的DOWN事件,该事件仍然会传递到该ViewGroup的onTouchEvent方法中。
  • 另外一方面,若是ViewGroup拦截了一个半路的事件(好比,MOVE),这个事件将会被系统变成一个CANCEL事件,并传递给以前处理该手势(gesture)的子View,并且不会再传递(不管是被拦截的MOVE仍是系统生成的CANCEL)给ViewGroup的onTouchEvent方法。只有再到来的事件才会传递到ViewGroup的onTouchEvent方法中。

今后开始,你能够更进一步。好比对mouthful-method (实在不知道该怎么翻译啦!)requestDisallowInterceptTouchEvent,C能够用该方法阻止B窃取事件。若是你想更加疯狂一点,你能够在你本身的ViewGroup中直接覆写dispatchTouchEvent方法,并对传递进来的事件作任何你想作的处理。但这样的话你可能会破坏一些约定,因此应当当心。

目录结构

参考文章

多是讲解Android事件分发最好的文章

相关文章
相关标签/搜索