Android:onInterceptTouchEvent和dispatchTouchEvent之间的区别?

时间:2012-03-06 14:52:51

标签: java android event-handling event-listener android-touch-event

Android中的onInterceptTouchEventdispatchTouchEvent有什么区别?

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

onInterceptTouchEventdispatchTouchEventonTouchEvent如何在视图层次结构(ViewGroup)内进行交互?

15 个答案:

答案 0 :(得分:261)

揭开这个神秘面纱的最佳地点是源代码。对于解释这个问题,文档非常不足。

dispatchTouchEvent实际上是在Activity,View和ViewGroup上定义的。 将其视为决定如何路由触摸事件的控制器。

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

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

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

您是否想要覆盖[Activity | ViewGroup | View] .dispatchTouchEvent?除非你做一些自定义路由,否则你可能不应该这样做。

主要的扩展方法是ViewGroup.onInterceptTouchEvent,如果你想在父级别监视和/或拦截触摸事件而使用View.onTouchListener / View.onTouchEvent进行主事件处理。

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

答案 1 :(得分:228)

因为这是Google的第一个结果。我希望与您分享Dave Smith在Youtube: Mastering the Android Touch System上的精彩演讲,并提供幻灯片here。它让我对Android Touch系统有了深刻的理解:

活动如何处理触摸:

  
      
  • Activity.dispatchTouchEvent()      
        
    • 始终首先被称为
    •   
    • 将事件发送到附加到Window的根视图
    •   
    • onTouchEvent()      
          
      • 如果没有视图消耗该事件,则调用
      •   
      • 永远被称为
      •   
    •   
  •   

查看如何处理触摸:

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

ViewGroup 如何处理触摸:

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

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

<强>答案: 基本上,dispatchTouchEvent()会在每个View图层上调用,以确定View是否对正在进行的手势感兴趣。在ViewGroup ViewGroupdispatchTouchEvent()能够在dispatchTouchEvent() - 方法中窃取触摸事件,然后才能在孩子身上调用ViewGroup。如果ViewGroup onInterceptTouchEvent() - 方法返回true,dispatchTouchEvent()将仅停止调度。 差异MotionEvents正在调度onInterceptTouchEventMotionEvent告诉它是否应该拦截(不会将public boolean dispatchTouchEvent(MotionEvent ev) { if(!onInterceptTouchEvent()){ for(View child : children){ if(child.dispatchTouchEvent(ev)) return true; } } return super.dispatchTouchEvent(ev); } 发送给儿童)< / em>或不(派遣给孩子)

你可以想象code of a ViewGroup或多或少做这个(非常简化):

{{1}}

答案 2 :(得分:43)

补充答案

以下是其他答案的视觉补充。我的完整答案是here

enter image description here

enter image description here

dispatchTouchEvent()的{​​{1}}方法使用ViewGroup选择是否应立即处理触摸事件(使用onInterceptTouchEvent())或继续通知onTouchEvent()其子女的方法。

答案 3 :(得分:17)

这些方法存在很多混淆,但实际上并不复杂。大多数混淆是因为:

  1. 如果您的View/ViewGroup或其任何子女未返回true 仅onTouchEventdispatchTouchEventonInterceptTouchEvent 被叫MotionEvent.ACTION_DOWN。没有真正的 onTouchEvent,父视图将假定您的视图不需要 MotionEvents。
  2. 如果ViewGroup的子节点都没有在onTouchEvent中返回true,则只会为MotionEvent.ACTION_DOWN调用onInterceptTouchEvent,即使ViewGroup在onTouchEvent中返回true也是如此。
  3. 处理顺序如下:

    1. dispatchTouchEvent被召唤。
    2. onInterceptTouchEvent是为MotionEvent.ACTION_DOWN或何时调用的 ViewGroup的任何子项都在onTouchEvent中返回true。
    3. onTouchEvent首先调用ViewGroup的子节点 当没有一个孩子返回true时,它被调用 View/ViewGroup
    4. 如果您想在不禁用孩子事件的情况下预览TouchEvents/MotionEvents,您必须做两件事:

      1. 覆盖dispatchTouchEvent以预览活动并返回 super.dispatchTouchEvent(ev);
      2. 覆盖onTouchEvent并返回true,否则您将无法获得任何内容 MotionEvent除了MotionEvent.ACTION_DOWN
      3. 如果您想检测某些手势,例如滑动事件,只要您没有检测到手势,就不会禁用您孩子的其他事件,您可以这样做:

        1. 如上所述预览MotionEvents并在您设置标志时 检测到你的手势。
        2. 当您的标记设置为取消时,在onInterceptTouchEvent中返回true 您孩子的MotionEvent处理。这也很方便 重置你的标志的地方,因为onInterceptTouchEvent不会 再次调用,直到下一个MotionEvent.ACTION_DOWN
        3. 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;
          }
          

答案 4 :(得分:8)

我在这个网页http://doandroids.com/blogs/tag/codeexample/处得到了非常直观的解释。取自那里:

  
      
  • boolean onTouchEvent(MotionEvent ev) - 每当检测到以此View为目标的触摸事件时调用
  •   
  • boolean onInterceptTouchEvent(MotionEvent ev) - 每当使用此ViewGroup或其子节点作为目标检测到触摸事件时调用。如果此函数返回true,则将拦截MotionEvent,这意味着它不会传递给子节点,而是传递给此视图的onTouchEvent。
  •   

答案 5 :(得分:8)

dispInTouchEvent在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

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

另一个区别是,如果dispatchTouchEvent返回'false',则事件不会传播给子节点,在本例中为EditText,而如果在onInterceptTouchEvent中返回false,则事件仍然会调度到EditText

答案 6 :(得分:4)

您可以在此视频https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19和接下来的3个视频中找到答案。所有的触摸事件都得到了很好的解释,它非常清晰且充满了实例。

答案 7 :(得分:3)

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;
  }

我使用自定义叠加层来防止背景视图响应触摸事件。

答案 8 :(得分:3)

简短回答: dispatchTouchEvent()将被称为首先

简短建议:不应该覆盖dispatchTouchEvent(),因为它很难控制,有时会降低您的效果。恕我直言,我建议覆盖onInterceptTouchEvent()

由于大多数答案都清楚地提到了活动/视图组/视图中的流触摸事件,我只在ViewGroup中添加了有关这些方法的代码的更多详细信息(忽略dispatchTouchEvent()):

首先调用 onInterceptTouchEvent() ,分别调用ACTION事件 - &gt;移动 - &gt;起来。有两种情况:

  1. 如果您在3种情况下返回错误(ACTION_DOWN,ACTION_MOVE,ACTION_UP),则会认为父级不需要此触摸事件,所以 onTouch()的父母从不打电话,但 onTouch()的孩子会打电话;但请注意:

    • onInterceptTouchEvent()仍然会继续接收触摸事件,只要其子女不会拨打requestDisallowInterceptTouchEvent(true)
    • 如果没有孩子接收到该事件(在两种情况下可能发生:用户触摸的位置没有孩子,或者有孩子,但在ACTION_DOWN时返回false),父母会将该事件发送回{{ 1}}父母。
  2. 反之亦然,如果您返回true 父级将立即窃取此触摸事件onTouch()将立即停止,而 onInterceptTouchEvent()父母将被召唤以及所有 onTouch()个孩子将接收最后一个动作事件 - ACTION_CANCEL (因此,这意味着父母偷了触摸事件,孩子们从那时起就无法处理它。 onTouch()返回false的流程是正常的,但返回true的情况有点混乱,所以我在这里列出:

    • 在ACTION_DOWN返回true,父母的onInterceptTouchEvent()将再次收到ACTION_DOWN 并执行以下操作(ACTION_MOVE,ACTION_UP)。
    • 在ACTION_MOVE返回true,父母的onTouch()将收到下一个 ACTION_MOVE(onTouch()中不同的ACTION_MOVE)以及后续操作(ACTION_MOVE,ACTION_UP)。
    • 在ACTION_UP返回true,父母的onInterceptTouchEvent() NOT ,因为父母偷窃触摸事件为时已晚。
  3. 重要的另一件事是onTouch()中事件的ACTION_DOWN将确定视图是否希望从该事件接收更多操作。如果视图在onTouch()中的ACTION_DOWN返回true,则表示视图愿意从该事件接收更多操作。否则,在onTouch()中的ACTION_DOWN返回false将意味着该视图将不再接收该事件的任何操作。

答案 9 :(得分:1)

主要区别:

  

•Activity.dispatchTouchEvent(MotionEvent) - 这允许您的Activity   在将所有触摸事件发送到之前拦截它们   窗口。
  •ViewGroup.onInterceptTouchEvent(MotionEvent) - 这允许a   ViewGroup在调度到子视图时观察事件。

答案 10 :(得分:1)

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

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

如果您从onInterceptTouchEvent()返回false,则会调用目标视图的onTouchEvent()。对于后续消息,将重复此操作,直到您从onInterceptTouchEvent()返回true为止。

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

答案 11 :(得分:0)

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

Android中的事件发送从Activity-&gt; ViewGroup-&gt; View。

开始

答案 12 :(得分:0)

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

答案 13 :(得分:0)

我认为dispatchTouchEvent()对于处理设备上的触摸非常重要。 ActivityViewGroupView具有此方法的实现。如果考虑到ViewGroup的方法,我们将看到dispatchTouchEvent()调用onInterceptTouchEvent()

enter image description here

SO的完整答案是here

答案 14 :(得分:-1)

小答案:

onInterceptTouchEvent出现在setOnTouchListener之前。