Android:onInterceptTouchEvent和dispatchTouchEvent之间的区别?


249

onInterceptTouchEventdispatchTouchEventAndroid 之间有什么区别?

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

在View()层次结构中如何进行互动onInterceptTouchEventdispatchTouchEvent以及如何onTouchEvent相互作用ViewGroup

Answers:


272

揭开神秘面纱的最佳地方是源代码。该文档不足以解释这一点。

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进行主事件处理。

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


10
这是一个很好的简洁答案。有关更详细的示例,请参见培训“在ViewGroup中管理触摸事件”
TalkLittle,2013年

1
@numan salati:“从此以后,父级的所有触摸事件都将分派到onTouchListener.onTouch”-我想在我的视图组中重写dispatch touch方法,并使它将事件分派给onTouchListener。但是我不知道该怎么做。没有像View.getOnTouchListener()。onTouch()这样的api。有一个setOnTouchListener()方法,但没有getOnTouchListener()方法。那怎么办呢?
阿什温

@Ashwin正是我的想法,也没有setOnInterceptTouchEvent。您可以覆盖子类视图以在布局中使用/在代码中添加它,但是您不能弄乱片段/活动级别的rootViews,因为如果不对片段/活动本身进行子类化就无法对这些视图进行子类化(例如,在大多数兼容性下) ProgressActivitiy实现)。为了简单起见,API需要使用setOnInterceptTouchEvent。每个人都在半复杂的应用程序中的某个时候在rootViews上使用interceptTouch
leRobot 2014年

244

因为这是Google的第一个结果。我想与您分享Dave Smith在YouTube上的精彩演讲:掌握Android触摸系统,并在此处找到幻灯片。它使我对Android触摸系统有了很好的深刻理解:

活动如何处理触摸:

  • Activity.dispatchTouchEvent()
    • 永远先被称为
    • 将事件发送到附加到Window的根视图
    • onTouchEvent()
      • 如果没有视图占用事件,则调用
      • 永远被召唤

视图如何处理触摸:

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

ViewGroup如何处理触摸:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • 检查是否应该取代孩子
      • 传递 ACTION_CANCEL 给活跃的孩子
      • 如果一次返回true,则ViewGroup消耗所有后续事件
    • 对于每个子视图(以相反的顺序添加)
      • 如果触摸是相关的(内部视图), child.dispatchTouchEvent()
      • 如果上一个未处理,则调度到下一个视图
    • 如果没有孩子处理该事件,则听者有机会
      • OnTouchListener.onTouch()
    • 如果没有侦听器,或其未处理
      • onTouchEvent()
  • 拦截事件跳过了子步骤

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

答: 基本上,dispatchTouchEvent()在每个View层上都会调用,以确定a View是否对正在进行的手势感兴趣。在ViewGroupViewGroup有偷他的触摸事件的能力dispatchTouchEvent()-方法,之前它会叫dispatchTouchEvent()上孩子。该ViewGroup如果只会停止调度ViewGroup onInterceptTouchEvent()-方法返回true。该区别是,dispatchTouchEvent()被调度MotionEventsonInterceptTouchEvent告诉它是否应拦截(不分派MotionEvent到子女)或没有(派遣儿童)

您可以想象一个ViewGroup代码差不多可以做到这一点(非常简化):

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

64

补充答案

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

在此处输入图片说明

在此处输入图片说明

dispatchTouchEvent()一个方法ViewGroup用途onInterceptTouchEvent()来选择是立即处理触摸事件(带有onTouchEvent())还是继续通知dispatchTouchEvent()其子对象的方法。


活动中也可以调用onInterceptTouchEvent吗?我认为只有在ViewGroup中才有可能,或者我错了?@Suragch
Federico Rizzo

@FedericoRizzo,你是对的!非常感谢你!我更新了图表和答案。
Suragch

20

这些方法有很多困惑,但实际上并不那么复杂。大多数的困惑是因为:

  1. 如果您View/ViewGroup还是其子的任何不返回true中 onTouchEventdispatchTouchEvent并且onInterceptTouchEvent将只被调用MotionEvent.ACTION_DOWN。如果没有true from onTouchEvent,则父视图将假定您的视图不需要MotionEvents。
  2. 如果ViewGroup的所有子级都没有在onTouchEvent中返回true,则仅会调用onInterceptTouchEvent MotionEvent.ACTION_DOWN,即使您的ViewGroup在中返回true onTouchEvent

处理顺序如下:

  1. dispatchTouchEvent 叫做。
  2. onInterceptTouchEventMotionEvent.ACTION_DOWN或在ViewGroup的任何子代返回true时被调用onTouchEvent
  3. onTouchEvent首先在ViewGroup的子级上调用,当所有子级都不返回true时,在上调用 View/ViewGroup

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

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

如果您想检测某些手势(如滑动事件),而又没有禁用孩子的其他事件(只要您没有检测到手势),则可以执行以下操作:

  1. 如上所述,预览MotionEvent,并在检测到手势时设置标志。
  2. onInterceptTouchEvent当您的标志设置为取消孩子的MotionEvent处理时,返回true 。这也是重置标志的方便位置,因为onInterceptTouchEvent直到next才会再次调用MotionEvent.ACTION_DOWN

中的覆盖FrameLayout示例(我在Xamarin Android上编程时的示例是C#,但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;
}

3
我不知道为什么没有更多的投票。这是一个很好的答案(IMO),我发现它非常有帮助。
Mark Ormesher 2014年

8

我在此网页http://doandroids.com/blogs/tag/codeexample/上遇到了非常直观的解释。从那里取:

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

2
问题是关于onInterceptTouchEvent和dispatchTouchEvent。两者都在onTouchEvent之前调用。但是在该示例中,您看不到dispatchTouchEvent。
Dayerman 2012年

8

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。


5

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

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


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

onInterceptTouchEvent()首先将被调用,ACTION事件将分别被向下调用->移动->向上。有两种情况:

  1. 如果在3种情况下(ACTION_DOWN,ACTION_MOVE,ACTION_UP)返回false,它将认为是父母不需要此触摸事件,因此onTouch()父母永不打电话,但onTouch()孩子会打电话 ; 但是请注意:

    • onInterceptTouchEvent()仍然继续接收触摸事件,只要它的孩子不叫requestDisallowInterceptTouchEvent(true)
    • 如果没有孩子收到该事件(在2种情况下可能发生:用户触摸的位置没有孩子,或者有孩子但在ACTION_DOWN处返回false),则父母会将事件发送回onTouch()父母。
  2. 反之亦然,如果返回true,则父级将立即窃取此触摸事件,并onInterceptTouchEvent()立即停止,而不是onTouch()调用父级,所有onTouch()孩子都会收到最后一个动作事件-ACTION_CANCEL(因此,这意味着父级偷走了触摸事件,从那以后孩子们将无法处理它)。onInterceptTouchEvent()return false 的流程是正常的,但是与return true的情况有些混淆,因此我在这里列出:

    • 在ACTION_DOWN返回true,onTouch()其中的父母将再次收到ACTION_DOWN及其后续动作(ACTION_MOVE,ACTION_UP)。
    • 如果在ACTION_MOVE返回true,则onTouch()其中的父母将收到下一个 ACTION_MOVE(与onInterceptTouchEvent())和后续操作(ACTION_MOVE,ACTION_UP)。
    • 如果在ACTION_UP返回true,则完全不会调用任何onTouch()父母,因为对于父母而言,窃取触摸事件为时已晚。

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



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

我将其与自定义叠加层一起使用,以防止背景视图响应触摸事件。


1

主要区别:

•Activity.dispatchTouchEvent(MotionEvent)-这使您的活动可以在将所有触摸事件分派到窗口之前对其进行拦截。
ViewGroup.onInterceptTouchEvent(MotionEvent)-这允许ViewGroup在将事件调度到子View时监视它们。


1
是的,我从android开发者指南中知道了这个答案-仍然不清楚。对于ViewGroup不仅存在于Activity,还存在dispatchTouchEvent方法。我的问题是,三个方法dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent在ViewGroups的层次结构(例如RelativeLayouts)中如何相互作用。
Anne Droid 2012年

1

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

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

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

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


0

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

Android中的事件分发从“活动”->“视图组”->“视图”开始。


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

1
你能补充一些解释吗?
Paul Floyd

3
尽管此代码段可以解决问题,但提供说明确实有助于提高您的帖子质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。
罗萨里奥·佩雷拉·费尔南德斯

-2

小答案:

onInterceptTouchEvent在setOnTouchListener之前。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.