onInterceptTouchEvent
和dispatchTouchEvent
Android 之间有什么区别?
根据android开发人员指南,这两种方法均可用于拦截触摸事件(MotionEvent
),但是有什么区别?
在View()层次结构中如何进行互动onInterceptTouchEvent
,dispatchTouchEvent
以及如何onTouchEvent
相互作用ViewGroup
?
onInterceptTouchEvent
和dispatchTouchEvent
Android 之间有什么区别?
根据android开发人员指南,这两种方法均可用于拦截触摸事件(MotionEvent
),但是有什么区别?
在View()层次结构中如何进行互动onInterceptTouchEvent
,dispatchTouchEvent
以及如何onTouchEvent
相互作用ViewGroup
?
Answers:
揭开神秘面纱的最佳地方是源代码。该文档不足以解释这一点。
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更倾向于灵活性而不是简单性。
因为这是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
是否对正在进行的手势感兴趣。在ViewGroup
该ViewGroup
有偷他的触摸事件的能力dispatchTouchEvent()
-方法,之前它会叫dispatchTouchEvent()
上孩子。该ViewGroup
如果只会停止调度ViewGroup
onInterceptTouchEvent()
-方法返回true。该区别是,dispatchTouchEvent()
被调度MotionEvents
和onInterceptTouchEvent
告诉它是否应拦截(不分派MotionEvent
到子女)或没有(派遣儿童)。
您可以想象一个ViewGroup的代码差不多可以做到这一点(非常简化):
public boolean dispatchTouchEvent(MotionEvent ev) {
if(!onInterceptTouchEvent()){
for(View child : children){
if(child.dispatchTouchEvent(ev))
return true;
}
}
return super.dispatchTouchEvent(ev);
}
这是其他答案的一些视觉补充。我的完整答案在这里。
在dispatchTouchEvent()
一个方法ViewGroup
用途onInterceptTouchEvent()
来选择是立即处理触摸事件(带有onTouchEvent()
)还是继续通知dispatchTouchEvent()
其子对象的方法。
这些方法有很多困惑,但实际上并不那么复杂。大多数的困惑是因为:
View/ViewGroup
还是其子的任何不返回true中
onTouchEvent
,dispatchTouchEvent
并且onInterceptTouchEvent
将只被调用MotionEvent.ACTION_DOWN
。如果没有true from
onTouchEvent
,则父视图将假定您的视图不需要MotionEvents。MotionEvent.ACTION_DOWN
,即使您的ViewGroup在中返回true onTouchEvent
。处理顺序如下:
dispatchTouchEvent
叫做。onInterceptTouchEvent
MotionEvent.ACTION_DOWN
或在ViewGroup的任何子代返回true时被调用onTouchEvent
。onTouchEvent
首先在ViewGroup的子级上调用,当所有子级都不返回true时,在上调用
View/ViewGroup
。如果要预览TouchEvents/MotionEvents
而不禁用孩子的事件,则必须做两件事:
dispatchTouchEvent
预览事件并返回
super.dispatchTouchEvent(ev)
;onTouchEvent
并返回true,否则您将不会获得
MotionEvent
除外MotionEvent.ACTION_DOWN
。如果您想检测某些手势(如滑动事件),而又没有禁用孩子的其他事件(只要您没有检测到手势),则可以执行以下操作:
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;
}
我在此网页http://doandroids.com/blogs/tag/codeexample/上遇到了非常直观的解释。从那里取:
- boolean onTouchEvent(MotionEvent ev)-每当检测到以此View为目标的触摸事件时调用
- boolean onInterceptTouchEvent(MotionEvent ev)-每当检测到以该ViewGroup或其子对象为目标的触摸事件时调用。如果此函数返回true,则将拦截MotionEvent,这意味着它不会传递给子级,而是传递给此View的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。
简短的回答: 首先dispatchTouchEvent()
将被称为。
简短建议:不应覆盖,dispatchTouchEvent()
因为它很难控制,有时会降低性能。恕我直言,我建议改写onInterceptTouchEvent()
。
因为大多数答案都非常清楚地提到了活动/视图组/视图上的流程触摸事件,所以我仅在ViewGroup
(忽略dispatchTouchEvent()
)中添加有关这些方法的代码的更多详细信息:
onInterceptTouchEvent()
首先将被调用,ACTION事件将分别被向下调用->移动->向上。有两种情况:
如果在3种情况下(ACTION_DOWN,ACTION_MOVE,ACTION_UP)返回false,它将认为是父母不需要此触摸事件,因此onTouch()
父母永不打电话,但onTouch()
孩子会打电话 ; 但是请注意:
onInterceptTouchEvent()
仍然继续接收触摸事件,只要它的孩子不叫requestDisallowInterceptTouchEvent(true)
。onTouch()
父母。反之亦然,如果返回true,则父级将立即窃取此触摸事件,并onInterceptTouchEvent()
立即停止,而不是onTouch()
调用父级,所有onTouch()
孩子都会收到最后一个动作事件-ACTION_CANCEL(因此,这意味着父级偷走了触摸事件,从那以后孩子们将无法处理它)。onInterceptTouchEvent()
return false 的流程是正常的,但是与return true的情况有些混淆,因此我在这里列出:
onTouch()
其中的父母将再次收到ACTION_DOWN及其后续动作(ACTION_MOVE,ACTION_UP)。onTouch()
其中的父母将收到下一个 ACTION_MOVE(与onInterceptTouchEvent()
)和后续操作(ACTION_MOVE,ACTION_UP)。onTouch()
父母,因为对于父母而言,窃取触摸事件为时已晚。还有一件重要的事情是该事件的ACTION_DOWN onTouch()
将确定视图是否希望从该事件中接收更多操作。如果该视图在中的ACTION_DOWN返回true onTouch()
,则表示该视图愿意从该事件中接收更多操作。否则,在ACTION_DOWN处返回false onTouch()
将意味着该视图将不再从该事件接收任何操作。
您可以在此视频https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19和接下来的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;
}
我将其与自定义叠加层一起使用,以防止背景视图响应触摸事件。
主要区别:
•Activity.dispatchTouchEvent(MotionEvent)-这使您的活动可以在将所有触摸事件分派到窗口之前对其进行拦截。
ViewGroup.onInterceptTouchEvent(MotionEvent)-这允许ViewGroup在将事件调度到子View时监视它们。
ViewGroup onInterceptTouchEvent()
始终是ACTION_DOWN
事件的入口点,这是第一个发生的事件。
如果您希望ViewGroup处理此手势,请从返回true onInterceptTouchEvent()
。返回true时,ViewGroup onTouchEvent()
将接收所有后续事件,直到next ACTION_UP
或ACTION_CANCEL
,并且在大多数情况下,ACTION_DOWN
and ACTION_UP
或之间的触摸事件ACTION_CANCEL
为ACTION_MOVE
通常被识别为滚动/翻转手势。
如果从返回false onInterceptTouchEvent()
,onTouchEvent()
则将调用目标视图。后续消息将重复此操作,直到从返回true onInterceptTouchEvent()
。
资料来源:http : //neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume =false;
if(onInterceptTouchEvent(ev){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
}