Android中的onInterceptTouchEvent
和dispatchTouchEvent
有什么区别?
根据android开发者指南,这两种方法都可以用来拦截触摸事件(MotionEvent
),但有什么区别?
onInterceptTouchEvent
,dispatchTouchEvent
和onTouchEvent
如何在视图层次结构(ViewGroup
)内进行交互?
答案 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
ViewGroup
中dispatchTouchEvent()
能够在dispatchTouchEvent()
- 方法中窃取触摸事件,然后才能在孩子身上调用ViewGroup
。如果ViewGroup
onInterceptTouchEvent()
- 方法返回true,dispatchTouchEvent()
将仅停止调度。 差异是MotionEvents
正在调度onInterceptTouchEvent
而MotionEvent
告诉它是否应该拦截(不会将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。
dispatchTouchEvent()
的{{1}}方法使用ViewGroup
选择是否应立即处理触摸事件(使用onInterceptTouchEvent()
)或继续通知onTouchEvent()
其子女的方法。
答案 3 :(得分:17)
这些方法存在很多混淆,但实际上并不复杂。大多数混淆是因为:
View/ViewGroup
或其任何子女未返回true
仅onTouchEvent
,dispatchTouchEvent
和onInterceptTouchEvent
被叫MotionEvent.ACTION_DOWN
。没有真正的
onTouchEvent
,父视图将假定您的视图不需要
MotionEvents。MotionEvent.ACTION_DOWN
调用onInterceptTouchEvent,即使ViewGroup在onTouchEvent
中返回true也是如此。处理顺序如下:
dispatchTouchEvent
被召唤。onInterceptTouchEvent
是为MotionEvent.ACTION_DOWN
或何时调用的
ViewGroup的任何子项都在onTouchEvent
中返回true。onTouchEvent
首先调用ViewGroup的子节点
当没有一个孩子返回true时,它被调用
View/ViewGroup
。如果您想在不禁用孩子事件的情况下预览TouchEvents/MotionEvents
,您必须做两件事:
dispatchTouchEvent
以预览活动并返回
super.dispatchTouchEvent(ev)
; onTouchEvent
并返回true,否则您将无法获得任何内容
MotionEvent
除了MotionEvent.ACTION_DOWN
。如果您想检测某些手势,例如滑动事件,只要您没有检测到手势,就不会禁用您孩子的其他事件,您可以这样做:
onInterceptTouchEvent
中返回true
您孩子的MotionEvent处理。这也很方便
重置你的标志的地方,因为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;
}
答案 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;起来。有两种情况:
如果您在3种情况下返回错误(ACTION_DOWN,ACTION_MOVE,ACTION_UP),则会认为父级不需要此触摸事件,所以 onTouch()
的父母从不打电话,但 onTouch()
的孩子会打电话;但请注意:
onInterceptTouchEvent()
仍然会继续接收触摸事件,只要其子女不会拨打requestDisallowInterceptTouchEvent(true)
。 反之亦然,如果您返回true ,父级将立即窃取此触摸事件,onTouch()
将立即停止,而 onInterceptTouchEvent()
父母将被召唤以及所有 onTouch()
个孩子将接收最后一个动作事件 - ACTION_CANCEL (因此,这意味着父母偷了触摸事件,孩子们从那时起就无法处理它。 onTouch()
返回false的流程是正常的,但返回true的情况有点混乱,所以我在这里列出:
onInterceptTouchEvent()
将再次收到ACTION_DOWN 并执行以下操作(ACTION_MOVE,ACTION_UP)。onTouch()
将收到下一个 ACTION_MOVE(onTouch()
中不同的ACTION_MOVE)以及后续操作(ACTION_MOVE,ACTION_UP)。onInterceptTouchEvent()
将 NOT ,因为父母偷窃触摸事件为时已晚。 重要的另一件事是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_UP
或ACTION_CANCEL
,并且在大多数情况下,ACTION_DOWN
和ACTION_UP
之间的触摸事件或ACTION_CANCEL
为ACTION_MOVE
,通常会被识别为滚动/抓取手势。
如果您从onInterceptTouchEvent()
返回false,则会调用目标视图的onTouchEvent()
。对于后续消息,将重复此操作,直到您从onInterceptTouchEvent()
返回true为止。
答案 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()
对于处理设备上的触摸非常重要。 Activity
,ViewGroup
,View
具有此方法的实现。如果考虑到ViewGroup
的方法,我们将看到dispatchTouchEvent()
调用onInterceptTouchEvent()
SO的完整答案是here
答案 14 :(得分:-1)
小答案:
onInterceptTouchEvent出现在setOnTouchListener之前。