onInterceptTouchEvent
和dispatchTouchEvent
这两个方法都可以用来拦截触摸事件(MotionEvent
),但它们有何区别呢?在一个视图层级结构中(
ViewGroup
),onInterceptTouchEvent
、dispatchTouchEvent
和onTouchEvent
三个方法是如何相互作用的呢?onInterceptTouchEvent
和dispatchTouchEvent
这两个方法都可以用来拦截触摸事件(MotionEvent
),但它们有何区别呢?ViewGroup
),onInterceptTouchEvent
、dispatchTouchEvent
和onTouchEvent
三个方法是如何相互作用的呢?最好解决这个问题的方法是查看源代码。文档对此的解释非常不足。
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 api更倾向于灵活性而不是简单性。
如何处理触摸事件的View:
Activity.dispatchTouchEvent()
- 始终首先被调用
- 将事件发送到连接到Window的根视图
onTouchEvent()
- 如果没有视图消耗事件,则调用
- 始终最后被调用
View.dispatchTouchEvent()
- 如果存在监听器,则先将事件发送到监听器。
View.OnTouchListener.onTouch()
- 如果未被消耗,则自行处理触摸事件。
View.onTouchEvent()
ViewGroup 如何处理触摸事件:
他还在github.com/devunwired/上提供了自定义触摸的示例代码。
ViewGroup.dispatchTouchEvent()
onInterceptTouchEvent()
- 检查是否应该替代子视图
- 将
ACTION_CANCEL
传递给活动子视图- 如果它返回 true 一次,则
ViewGroup
消耗所有后续事件- 对于每个子视图(以添加的相反顺序)
- 如果触摸是相关的(在视图内部),则
child.dispatchTouchEvent()
- 如果它不是被之前处理的,则将其分派到下一个视图
- 如果没有子视图处理事件,则侦听器有机会
OnTouchListener.onTouch()
- 如果没有侦听器,或者它未被处理
onTouchEvent()
- 拦截的事件跳过了子级步骤
dispatchTouchEvent()
会在每个View
层上调用,以确定View
是否对正在进行的手势感兴趣。在ViewGroup
中,ViewGroup
有能力在其dispatchTouchEvent()
方法中窃取触摸事件,然后再调用子级的dispatchTouchEvent()
。只有当ViewGroup
的onInterceptTouchEvent()
方法返回true时,ViewGroup
才会停止分发。 区别在于,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);
}
以下是其他答案的可视化补充。我的完整答案在这里。
ViewGroup
的dispatchTouchEvent()
方法使用onInterceptTouchEvent()
来选择它是否应立即处理触摸事件(使用onTouchEvent()
)或继续通知其子项的dispatchTouchEvent()
方法。
View/ViewGroup
或其任何子项在onTouchEvent
中没有返回true,则dispatchTouchEvent
和onInterceptTouchEvent
仅将被调用一次,即MotionEvent.ACTION_DOWN
。如果onTouchEvent
没有返回true,则父视图会认为你的视图不需要MotionEvents。onTouchEvent
中返回true,onInterceptTouchEvent
也仅将被调用一次,即MotionEvent.ACTION_DOWN
。dispatchTouchEvent
。onTouchEvent
中返回true,或者MotionEvent.ACTION_DOWN
时,将调用onInterceptTouchEvent
。onTouchEvent
,当没有子项返回true时,将在View/ViewGroup
上调用onTouchEvent
。dispatchTouchEvent
以预览事件并返回super.dispatchTouchEvent(ev)
。onTouchEvent
并返回true,否则你将无法获得任何MotionEvent,除了MotionEvent.ACTION_DOWN
。onInterceptTouchEvent
中返回true。这也是重置标志的方便位置,因为onInterceptTouchEvent
不会再次调用,直到下一个MotionEvent.ACTION_DOWN
。FrameLayout
中覆盖的示例(我的示例是C#,因为我正在使用Xamarin Android进行编程,但逻辑相同):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;
}
简短回答: 首先会调用dispatchTouchEvent()
。
简短建议: 不要重写dispatchTouchEvent()
,因为它很难控制,有时会降低性能。我建议覆盖onInterceptTouchEvent()
。
由于大多数答案已经比较清楚地提到了在activity/view group/view上的触摸事件流程,因此我只添加一些在ViewGroup
这些方法中的代码细节(忽略dispatchTouchEvent()
):
onInterceptTouchEvent()
将首先被调用,依次调用ACTION事件down -> move -> up。有两种情况:
如果您在3个情况下(ACTION_DOWN、ACTION_MOVE、ACTION_UP)返回false,则视为父项不需要此触摸事件,因此父项的onTouch()
从未被调用,但是子项的onTouch()
将代替调用;但请注意:
requestDisallowInterceptTouchEvent(true)
,onInterceptTouchEvent()
仍将继续接收触摸事件。onTouch()
。反之,如果您返回true,则父项将立即窃取此触摸事件,并且onInterceptTouchEvent()
将立即停止,而是调用父项的onTouch()
以及所有子项的onTouch()
将接收到最后的操作事件-ACTION_CANCEL (因此它意味着父项窃取了触摸事件,从那时起子项无法处理它)。onInterceptTouchEvent()
返回false的流程很正常,但是对于返回true的情况有点混乱,因此我在这里列出:
onTouch()
将再次接收到ACTION_DOWN和后续动作(ACTION_MOVE、ACTION_UP)。onTouch()
方法将接收到下一个ACTION_MOVE事件(而不是在onInterceptTouchEvent()
中的同一个ACTION_MOVE事件)以及随后的操作(ACTION_MOVE, ACTION_UP)。onTouch()
方法。还有一件重要的事情是,在onTouch()
方法中的ACTION_DOWN事件将决定视图是否希望接收来自该事件的更多操作。如果视图在onTouch()
方法的ACTION_DOWN事件中返回true,则表示该视图愿意从该事件中接收更多操作。否则,在onTouch()
方法的ACTION_DOWN事件中返回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
如果您正在使用这两个处理程序,请使用dispatchTouchEvent来处理事件的第一个实例,该事件将传递到onInterceptTouchEvent。
另一个区别是,如果dispatchTouchEvent返回“false”,则事件不会传播到子项(在此情况下为EditText),而如果您在onInterceptTouchEvent中返回false,则事件仍将被分派到EditText。
@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;
}
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume =false;
if(onInterceptTouchEvent(ev){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
}