Android: onInterceptTouchEvent和dispatchTouchEvent的区别?

Android中onInterceptTouchEventdispatchTouchEvent的区别是什么?

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

onInterceptTouchEventdispatchTouchEventonTouchEvent如何在视图层次结构(ViewGroup)中相互作用?

129170 次浏览

主要区别:

•Activity. dispatchtouchevent (MotionEvent) -这允许您的Activity. dispatchtouchevent 来拦截所有的触摸事件,然后再将它们分派到 窗口。< br > •ViewGroup.onInterceptTouchEvent(MotionEvent) -这允许一个

. ViewGroup用于在事件分派给子视图时监视事件

我在这个网页上找到了非常直观的解释http://doandroids.com/blogs/tag/codeexample/。从这里开始:

  • boolean onTouchEvent(MotionEvent ev) -当检测到以该视图为目标的触摸事件时调用
  • boolean onInterceptTouchEvent(MotionEvent ev) -当一个触摸事件被检测到并以这个ViewGroup或它的子对象作为目标时调用。如果这个函数返回true, MotionEvent将被拦截,这意味着它不会被传递给子视图,而是传递给这个视图的onTouchEvent。

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之前处理。

用这个简单的例子:

   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

揭开这一神秘面纱的最佳地点是源代码。医生们对这一点解释得很不充分。

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

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

对于ViewGroup.dispatchTouchEvent来说,事情要复杂得多。它需要找出它的哪个子视图应该获得事件(通过调用child. dispatchtouchevent)。这基本上是一个命中测试算法,其中你计算出哪个子视图的边界矩形包含触点坐标。

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

你甚至想要覆盖[活动|ViewGroup|View].dispatchTouchEvent?除非您正在进行一些自定义路由,否则您可能不应该这样做。

主要的扩展方法是ViewGroup。onInterceptTouchEvent(如果你想监视和/或拦截父级的触摸事件)和View. ontouchlistener /View。onTouchEvent用于主事件处理。

总的来说,在我看来,它的设计过于复杂,但android api更倾向于灵活性而不是简单性。

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

Android中的事件调度从Activity->ViewGroup->View开始。

关于这些方法有很多困惑,但实际上并没有那么复杂。大部分的困惑是因为:

  1. 如果你的View/ViewGroup或它的任何子元素在 onTouchEventdispatchTouchEventonInterceptTouchEvent只能 MotionEvent.ACTION_DOWN调用。没有真正的 onTouchEvent,父视图将假设你的视图不需要 李MotionEvents。< / >
  2. 当一个ViewGroup的所有子类在onTouchEvent中都没有返回true时,onInterceptTouchEvent只会在MotionEvent.ACTION_DOWN中被调用,即使你的ViewGroup在onTouchEvent中返回true。

处理顺序如下:

  1. dispatchTouchEvent被调用。
  2. onInterceptTouchEvent用于MotionEvent.ACTION_DOWN或 ViewGroup的任何子对象在onTouchEvent中返回true
  3. onTouchEvent首先在ViewGroup和的子对象上调用 当没有子函数返回true时,调用 李View/ViewGroup。< / >

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

  1. 覆盖dispatchTouchEvent预览事件并返回 李super.dispatchTouchEvent(ev); < / >
  2. 覆盖onTouchEvent并返回true,否则将得不到任何值 MotionEvent except MotionEvent.ACTION_DOWN.

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

  1. 预览如上所述的MotionEvents,并设置一个标志 检测到您的手势。
  2. 当你的标志被设置为取消时,在onInterceptTouchEvent中返回true 你的孩子的动作事件处理。这也是一种方便 重置标志的位置,因为onInterceptTouchEvent不会被重置 再次调用,直到下一个MotionEvent.ACTION_DOWN.

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

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

如果你想让ViewGroup处理这个手势,从onInterceptTouchEvent()返回true。 在返回true时,ViewGroup的onTouchEvent()将接收到下一个ACTION_UPACTION_CANCEL之前的所有后续事件,并且在大多数情况下,ACTION_DOWNACTION_UPACTION_CANCEL之间的触摸事件是ACTION_MOVE,这通常会被识别为滚动/抛动手势

如果你从onInterceptTouchEvent()返回false,目标视图的onTouchEvent()将被调用。它将在后续消息中重复,直到你从onInterceptTouchEvent()返回true。

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

因为这是谷歌的第一个结果。我想和大家分享Dave Smith关于Youtube:掌握Android触摸系统的精彩演讲,幻灯片可以在在这里找到。让我对Android Touch System有了一个很深刻的了解:

活动如何处理触摸:

    <李> Activity.dispatchTouchEvent()
    • 总是第一个被叫到
    • 发送事件到附加到窗口的根视图
    • <李> onTouchEvent()
      • 如果没有视图使用该事件则调用
      • 总是最后一个被叫
      • 李< / ul > < / > 李< / ul > < / >

视图如何处理触摸:

    <李> View.dispatchTouchEvent()
    • 如果存在,首先向监听器发送事件
      • View.OnTouchListener.onTouch()
      • 李< / ul > < / >
      • 如果未被消耗,则处理触摸本身
        • View.onTouchEvent()
        • 李< / ul > < / > 李< / ul > < / >

ViewGroup如何处理触摸:

    <李> ViewGroup.dispatchTouchEvent()
      <李> onInterceptTouchEvent()
      • 检查它是否应该取代子元素
      • ACTION_CANCEL传递给活动的子节点
      • 如果它返回true一次,ViewGroup将消耗所有后续事件
      • 李< / ul > < / >
      • 对于每个子视图(按反向顺序添加)
        • 如果touch是相关的(内部视图),child.dispatchTouchEvent()
        • 如果它没有被前一个视图处理,分派到下一个视图
        • 李< / ul > < / >
        • 如果没有子事件处理该事件,则监听器将获得一个机会
          • OnTouchListener.onTouch()
          • 李< / ul > < / >
          • 如果没有监听器,或者没有处理
            • onTouchEvent()
            • 李< / ul > < / > 李< / ul > < / >
            • 被拦截的事件跳过子步骤

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

< >强答: 基本上,每个View层都会调用dispatchTouchEvent(),以确定View是否对正在进行的手势感兴趣。在ViewGroup中,ViewGroup有能力在他的dispatchTouchEvent()-方法中窃取触摸事件,然后才会对子对象调用dispatchTouchEvent()。只有当ViewGroup onInterceptTouchEvent()-method返回true时,ViewGroup才会停止分派。View4是__abc0正在分派View1,而View2告诉它是否应该拦截View5或不应该拦截View6

你可以想象ViewGroup的代码做的或多或少是这样的(非常简化):

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

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

答:小

onInterceptTouchEvent出现在setOnTouchListener之前。

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

补充回答

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

enter image description here

enter image description here

ViewGroupdispatchTouchEvent()方法使用onInterceptTouchEvent()来选择是立即处理触摸事件(使用onTouchEvent())还是继续通知其子对象的dispatchTouchEvent()方法。

简短的回答: dispatchTouchEvent()将被称为所有的第一个

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


因为大多数答案都非常清楚地提到了活动/视图组/视图上的流触摸事件,所以我只在ViewGroup中添加了关于这些方法的代码的更多细节(忽略dispatchTouchEvent()):

onInterceptTouchEvent()将首先被调用,ACTION事件将分别被调用down -> move -> up。有2种情况:

  1. 如果你在3种情况下返回假 (ACTION_DOWN, ACTION_MOVE, ACTION_UP),它将被认为是父节点不需要这个触摸事件,所以onTouch()的父母从不打电话,但onTouch()的子函数会调用;但请注意:

    • onInterceptTouchEvent()仍然继续接收触摸事件,只要它的子对象不调用requestDisallowInterceptTouchEvent(true)
    • 如果没有子节点在接收该事件(有两种情况:在用户触摸的位置没有子节点,或者有子节点但在ACTION_DOWN处返回false),父节点会将该事件发送回父节点的onTouch()
    • 李< / ul > < / >
    • 反之亦然,如果你还真,立即父级会窃取这个触摸事件onInterceptTouchEvent()将立即停止,而不是父类的onTouch()将被调用以及所有onTouch()的孩子将收到的最后一个动作事件- ACTION_CANCEL(因此,这意味着父母窃取了触摸事件,从那时起,孩子不能处理它)。onInterceptTouchEvent()返回false的流程是正常的,但与返回true的情况有点混淆,所以我在这里列出了它:

      • 在ACTION_DOWN处返回true,父类的onTouch()将接收ACTION_DOWN 再一次和随后的动作(ACTION_MOVE, ACTION_UP)。
      • 在ACTION_MOVE处返回true,父类的onTouch()将接收下一个 ACTION_MOVE(与onInterceptTouchEvent()中的ACTION_MOVE不相同)和后续的动作(ACTION_MOVE, ACTION_UP)。
      • 在ACTION_UP处返回true,父类的onTouch()将会被调用,因为父类已经来不及窃取触摸事件了。
      • 李< / ul > < / >

还有一件事重要的onTouch()中事件的ACTION_DOWN将决定视图是否希望从该事件接收更多操作。如果视图在onTouch()中的ACTION_DOWN处返回true,这意味着视图愿意从该事件接收更多操作。否则,在onTouch()中的ACTION_DOWN处返回false将暗示视图将不再从该事件接收任何动作。