Android:onInterceptTouchEvent和dispatchTouchEvent之间的区别?

Android中的onInterceptTouchEventdispatchTouchEvent什么区别?

根据android开发者指南,这两种方法都可以用来拦截触摸事件( MotionEvent ),但是有什么区别?

onInterceptTouchEventdispatchTouchEventonTouchEvent如何在onInterceptTouchEvent的层次结构( ViewGroup )中一起交互?

揭示这个秘密的最好的地方就是源代码。 这个文档对于解释这个问题是非常不充分的。

dispatchTouchEvent实际上是在Activity,View和ViewGroup上定义的。 把它看作是决定如何路由触摸事件的控制器。

例如,最简单的情况就是View.dispatchTouchEvent ,它会将触摸事件路由到OnTouchListener.onTouch(如果已定义)或扩展方法onTouchEvent

对于ViewGroup.dispatchTouchEvent事情更复杂。 它需要找出哪个子视图应该得到事件(通过调用child.dispatchTouchEvent)。 这基本上是一个命中testingalgorithm,您可以确定哪个子视图的边界矩形包含触点坐标。

但是在将事件分派给适当的子视图之前,父母可以一起窥探和/或拦截事件。 这是onInterceptTouchEvent的用途。 所以它在进行命中testing之前先调用这个方法,如果事件被劫持(通过从onInterceptTouchEvent返回true),它将ACTION_CANCEL发送到子视图,以便他们可以放弃他们的触摸事件处理(从先前的触摸事件)父级别的所有触摸事件都被分派到onTouchListener.onTouch (如果已定义)或onTouchEvent ()。 在这种情况下,onInterceptTouchEvent也不会再被调用。

你甚至想要重写[Activity | ViewGroup | View] .dispatchTouchEvent? 除非你正在做一些自定义路由你可能不应该。

如果要在父级别侦听和/或拦截触摸事件,并且要对主要事件处理进行View.onTouchListener / View.onTouchEvent,则主要的扩展方法是ViewGroup.onInterceptTouchEvent。

总而言之,它的devise过于复杂,但是android apis比简单更倾向于灵活性。

因为这是Google的第一个结果。 我想和你分享一下Dave Smith在Youtube上的精彩演讲:掌握Android Touch系统和幻灯片可以在这里find 。 这让我对Android Touch系统有了很深入的了解:

活动如何处理触摸:

  • Activity.dispatchTouchEvent()
    • 总是先被调用
    • 将事件发送到附加到窗口的根视图
    • onTouchEvent()
      • 如果没有视图消耗事件,则调用
      • 总是最后被调用

视图如何处理触摸:

  • View.dispatchTouchEvent()
    • 如果存在,则首先将事件发送给侦听器
      • View.OnTouchListener.onTouch()
    • 如果不消耗,则处理触摸本身
      • View.onTouchEvent()

ViewGroup如何处理触摸:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • 检查它是否应取代儿童
      • ACTION_CANCEL传递给活动的孩子
      • 返回一次,消耗所有后续事件
    • 对于每个子视图,按相反顺序添加它们
      • 如果touch是相关的(在视图内), child.dispatchTouchEvent()
      • 如果以前没有处理,则发送到下一个视图
    • 如果没有孩子处理事件,听众有机会
      • OnTouchListener.onTouch()
    • 如果没有听众,或没有处理
      • onTouchEvent()
  • 截获的事件跳过子步骤

他还在github.com/devunwired/上提供了自定义触摸的示例代码。

答:基本上,在每个View图层上调用dispatchTouchEvent()以确定View是否对正在进行的手势感兴趣。 在ViewGroupViewGroup可以在dispatchTouchEvent()方法中窃取触摸事件,然后调用子项的dispatchTouchEvent()ViewGroup只会在ViewGroup onInterceptTouchEvent()方法返回true时停止调度。 不同之处在于dispatchTouchEvent()派发onInterceptTouchEventonInterceptTouchEvent告诉它是否应该截取(不派发MotionEvent给子项)或不派发给子项

你可以想象一个ViewGroup的代码或多或less的这个(非常简化):

 public boolean dispatchTouchEvent(MotionEvent ev) { if(!onInterceptTouchEvent()){ for(View child : children){ if(child.dispatchTouchEvent(ev)) return true; } } return super.dispatchTouchEvent(ev); } 

这些方法有很多困惑,但其实并不复杂。 大部分的困惑是因为:

  1. 如果您的View/ViewGroup或其任何子项在onTouchEvent不返回true,则onTouchEvent dispatchTouchEventonInterceptTouchEvent 。 如果没有来自onTouchEvent的true,则父视图将假定您的视图不需要MotionEvents。
  2. 当ViewGroup的所有子元素都不会在onTouchEvent中返回true时,即使您的ViewGroup在onTouchEvent返回true,也onTouchEvent

处理顺序是这样的:

  1. dispatchTouchEvent被调用。
  2. onInterceptTouchEvent调用onInterceptTouchEvent或ViewGroup的onTouchEventonTouchEvent返回true时。
  3. 首先在ViewGroup的子项上调用onTouchEvent ,当没有一个子项返回true时,它将在View/ViewGroup上调用。

如果你想预览TouchEvents/MotionEvents而不禁用你的孩子的事件,你必须做两件事:

  1. 覆盖dispatchTouchEvent以预览事件并返回super.dispatchTouchEvent(ev) ;
  2. 重写onTouchEvent并返回true,否则除onTouchEvent ,您将不会获得任何MotionEvent.ACTION_DOWN

如果你想检测一个像滑动事件一样的手势,只要你没有检测到手势,就不会禁用你的孩子上的其他事件,你可以这样做:

  1. 如上所述预览MotionEvents,并在检测到您的手势时设置一个标志。
  2. 当您的标志设置为取消您的孩子的onInterceptTouchEvent时,在onInterceptTouchEvent返回true。 这也是重置你的标志的一个方便的地方,因为在下一个MotionEvent.ACTION_DOWN之前,onInterceptTouchEvent不会再被调用。

FrameLayout中覆盖的例子(我的例子是C#,因为我使用Xamarin Android进行编程,但是Java中的逻辑是一样的):

 public override bool DispatchTouchEvent(MotionEvent e) { // Preview the touch event to detect a swipe: switch (e.ActionMasked) { case MotionEventActions.Down: _processingSwipe = false; _touchStartPosition = e.RawX; break; case MotionEventActions.Move: if (!_processingSwipe) { float move = e.RawX - _touchStartPosition; if (move >= _swipeSize) { _processingSwipe = true; _cancelChildren = true; ProcessSwipe(); } } break; } return base.DispatchTouchEvent(e); } public override bool OnTouchEvent(MotionEvent e) { // To make sure to receive touch events, tell parent we are handling them: return true; } public override bool OnInterceptTouchEvent(MotionEvent e) { // Cancel all children when processing a swipe: if (_cancelChildren) { // Reset cancel flag here, as OnInterceptTouchEvent won't be called until the next MotionEventActions.Down: _cancelChildren = false; return true; } return false; } 

我在这个网页http://doandroids.com/blogs/tag/codeexample/上find了非常直观的解释。; 从那里采取:

  • boolean onTouchEvent(MotionEvent ev) – 只要检测到以此View为目标的触摸事件就被调用
  • boolean onInterceptTouchEvent(MotionEvent ev) – 只要检测到该ViewGroup或其子对象的触摸事件,就调用它。 如果此函数返回true,那么MotionEvent将被截取,这意味着它不会被传递给子视图,而是传递给此视图的onTouchEvent。

dispatchTouchEvent在onInterceptTouchEvent之前处理。

使用这个简单的例子:

  main = new LinearLayout(this){ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { System.out.println("Event - onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); //return false; //event get propagated } @Override public boolean dispatchTouchEvent(MotionEvent ev) { System.out.println("Event - dispatchTouchEvent"); return super.dispatchTouchEvent(ev); //return false; //event DONT get propagated } }; main.setBackgroundColor(Color.GRAY); main.setLayoutParams(new LinearLayout.LayoutParams(320,480)); viewA = new EditText(this); viewA.setBackgroundColor(Color.YELLOW); viewA.setTextColor(Color.BLACK); viewA.setTextSize(16); viewA.setLayoutParams(new LinearLayout.LayoutParams(320,80)); main.addView(viewA); setContentView(main); 

你可以看到日志会像

 I/System.out(25900): Event - dispatchTouchEvent I/System.out(25900): Event - onInterceptTouchEvent 

因此,如果您正在使用这2个处理程序,请使用dispatchTouchEvent来处理第一个实例的事件,该事件将转至onInterceptTouchEvent。

另一个区别是,如果dispatchTouchEvent返回“false”事件不传播给子,在这种情况下EditText,而如果你在onInterceptTouchEvent返回false事件仍然得到调度到EditText

您可以在这个video中find答案https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19和接下来的3个video。; 所有的触摸事件都解释得很好,这个例子非常清楚和充满。

ViewGroup子类中的以下代码将阻止其父容器接收触摸事件:

  @Override public boolean dispatchTouchEvent(MotionEvent ev) { // Normal event dispatch to this container's children, ignore the return value super.dispatchTouchEvent(ev); // Always consume the event so it is not dispatched further up the chain return true; } 

我用这个自定义覆盖,以防止背景视图响应触摸事件。

补充答案

以下是对其他答案的一些视觉补充。 我的完整答案在这里 。

在这里输入图像说明

在这里输入图像说明

dispatchTouchEvent()方法使用onInterceptTouchEvent()来select是否立即处理触摸事件(使用onTouchEvent() )或继续通知其子项的dispatchTouchEvent()方法。

主要区别:

•Activity.dispatchTouchEvent(MotionEvent) – 这允许您的Activity在发送到窗口之前拦截所有触摸事件。
•ViewGroup.onInterceptTouchEvent(MotionEvent) – 这允许ViewGroup在事件被分派到子视图时观察事件。

ViewGroup的onInterceptTouchEvent()始终是ACTION_DOWN事件发生的第一个事件的入口点。

如果您希望ViewGroup处理此手势,请从onInterceptTouchEvent()返回true。 在返回true时,ViewGroup的onTouchEvent()将接收所有后续事件,直到下一个ACTION_UPACTION_CANCEL ,并且在大多数情况下, ACTION_DOWNACTION_UPACTION_CANCEL之间的触摸事件是ACTION_MOVE ,通常将其识别为滚动/ ACTION_MOVE手势。

如果从onInterceptTouchEvent()返回false, onInterceptTouchEvent()目标视图的onTouchEvent() 。 对于随后的消息,它将被重复,直到从onInterceptTouchEvent()返回true。

来源: http : //neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html

Activity和View都有方法dispatchTouchEvent()和onTouchEvent。ViewGroup也有这个方法,但是有另外一个叫做onInterceptTouchEvent的方法。 这些方法的返回types是布尔型的,可以通过返回值来控制调度路由。

Android中的事件派发从Activity-> ViewGroup-> View开始。

 public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume =false; if(onInterceptTouchEvent(ev){ consume = onTouchEvent(ev); }else{ consume = child.dispatchTouchEvent(ev); } } 

小答案:

onInterceptTouchEvent在setOnTouchListener之前。