转载请注明出处:http://blog.csdn.net/fishle123/article/details/50638795
在 Android UI 开发中,经常涉及touch(触摸)事件和手势。最经常使用的点击事件(OnClickListener)也与 touch 事件相关。因此,理解 touch 事件在 View 层级中的传递机制尤为重要。然而,dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent等一系列接口方法很容易让人混淆。
本文将介绍 touch 事件的一些基础知识,并通过分析 Android FrameWork 源码来深入理解 touch 事件的分发机制。
一、MotionEvent与事件分发相关的方法
1.MotionEvent
MotionEvent类封装了一个 Touch 事件的相关参数,我们通常所说的一个 Touch 事件,就是通过一个MotionEvent类的实例来描述。一个 MotionEvent 可以分为多种类型,即 ACTION_DOWN(按下)、ACTION_MOVE(移动)、ACTION_UP(抬起)和 ACTION_CANCEL(取消)等。
1)ACTION_DOWN:
一般来说,通常的 Touch 事件触发的流程都是 DOWN -->UP,或者 DOWN -->MOVE-->...-->MOVE--> UP。所以 ACTION_DOWN 事件通常都是一系列连续操作事件的起点,MOVE事件是否能够被触发取决于操作手势里面是否包含了移动的动作。
2)ACTION_MOVE:
当手指按下后在屏幕上移动时,就会产生 ACTION_MOVE 事件,并且通常会随着手指移动而连续产生很多个。
3)ACTION_UP:
UP 是一系列手势操作的结束点,程序会在收到 ACTION_UP 事件时做一些收尾性的工作,例如恢复 View 的点击状态,值得一提的是,View 的 onClick 方法就是在 ACTION_UP 时加以判断满足其他条件之后被触发的。
4)ACTION_CANCEL:
CANCEL 事件不是由用户触发的,而是系统经过逻辑判断后对某个 View 发送“取消”消息时产生的。收到 CANCEL 事件时,View 应该负责将自己的状态恢复。
2.事件分发方法
public boolean dispatchTouchEvent(MotionEvent ev):
事件由上一层的 View 传递到下一层 View 的过程称为事件分发。dispatchTouchEvent 方法负责事件分发。Activity、ViewGroup、View 类中都定义了该方法,所以它们都具有事件分发的能力。
Activity.dispatchTouchEvent 实际上是调用了 DecorView 的 dispatchTouchEvent 方法,而 DecorView 实际上是一个 FrameLayout,因此,Activity 的 dispatchTouchEvent 最终也是调用到了 ViewGroup 的 dispatchTouchEvent 方法。
另外,由于 View 没有管理子 View 的能力,所以 View.dispatchTouchEvent 方法实际上不是用来向下分发事件,而是将事件分发给自己,调用了自己的事件响应方法去响应事件。
3.事件拦截
public boolean onInterceptTouchEvent(MotonEvent event)
事件在 ViewGroup 的分发过程中,ViewGroup 可以决定是否拦截事件而不对子 View 分发。该方法的返回值决定是否需要拦截的,返回 true 表示拦截,false 表示不拦截。该方法只定义在 ViewGroup 类中.
4.事件响应方法
public boolean onTouchEvent(MotionEvent event):
该方法负责响应事件,并且返回一个 boolean 型,表示是否消费掉事件,返回 true 表示消费,false 表示不消费。Activity、View、ViewGroup 都有这个方法,所以它们都具有事件响应的能力,并且通过返回值来表示事件是否已经消费。如果子View没有消费掉Touch事件,那么会传递给上一层的View来处理。
消息分发流程,从上到下,从父到子:Activity->ViewGroup1->ViewGroup1的子ViewGroup2->…->Target View
消息响应流程,从下到上,从子到父:Target View->…->ViewGroup1的子ViewGroup2->ViewGroup1->Activity
二、View 中 Touch 事件的分发逻辑
先来看 一下View.dispatchTouchEvent 的源码:
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//只要view设置了OnTouchListerner并且enable了,那么就调用OnTouchListener.onTouch来处理
result = true;
}
//如果view没有设置OnTouchListener或者OnTouchListener.onTouch返回false(表明没有消费该Touch事件),那么调用View的onTouchEvent来处理
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
......
return result;
}
通过上面的注释可以看到:View 的事件分发过程主要涉及两个方法:mOnTouchListener.onTouch 和 onTouchEvent,并且当 mOnTouchListener 存在时,mOnTouchListener.onTouch 调用的优先级比较高。
而mOnTouchListener 可以通过 View 的 setOnTouchListener(OnTouchListener l) 方法中来设置。当我们通过 setOnTouchListener(OnTouchListener l) 方法设置了 onClickListener,并在 onClickListener.onTouch 方法中返回 true 消费了事件之后,onTouchEvent 将不会再被调用。
mOnTouchListener.onTouch 是由外部 设置到 View 里去的,而 onTouchEvent 只能通过 Override 去重写自己的逻辑,且 View 的 onTouchEvent 方法自身已经有不少逻辑。所以 mOnTouchListener.onTouch 更适合添加一些不太复杂的 touch 逻辑,并且可以不妨碍 onTouchEvent 的正常调用;而 onTouchEvent 更适用于用 Override 的形式来改变 View 本身 touch 逻辑。
三、ViewGroup 中 Touch 事件的分发逻辑
虽然 ViewGroup 是 View 的子类,但是因为 ViewGroup 涉及对子 View 的处理,所以其事件分发逻辑比 View 的分发逻辑会复杂很多。
看 ViewGroup.dispatchTouchEvent 的源码:
</pre><pre name="code" class="java">@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//处理初始的ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN) {
//清除之前的手势
cancelAndClearTouchTargets(ev);
resetTouchState();//核心操作mFirstTouchTarget=null
}
//检查是否拦截touch事件
final boolean intercepted;
/*是ACTION_DOWN事件或者mFirstTouchTarget!=null(已经找到事件的接收者),如果已经拦截了ACTION_DOWN事件,那么mFisrtTouchTarget必然为null,后续的Touch事件将直接由当前ViewGroup处理,不会再询问是否拦截*/
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//判断是否允许拦截,因为requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法可以禁止执行是否需要拦截的判断
if (!disallowIntercept) {
// 禁止拦截的FLAG为false,说明可以执行拦截判断,则执行此ViewGroup的onInterceptTouchEvent方法
intercepted = onInterceptTouchEvent(ev);// 该方法默认返回false,如果想修改默认的行为,需要override此方法,修改返回值。
ev.setAction(action); // restore action in case it was changed
} else {
// 禁止拦截的FLAG为ture,说明没有必要去执行是否需要拦截了,这个事件是无法拦截的,所以设置拦截变量为false
intercepted = false;
}
} else {
// 当不是ACTION_DOWN事件并且mFirstTouchTarget为null(意味着没有touch的目标组件)时,这个ViewGroup应该继续执行拦截的操作。
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
//检查是否取消当前ACTION的处理,如果resetCancelNextUpFlag返回为true表明取消,或者当前ACTION为ACTION_CANCEL
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
//非取消事件 并且不需要拦截事件,分发给子View处理
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
// 在ACTION_DOWN时去寻找这次DOWN事件新出现的TouchTarget
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
......
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
// 根据触摸的坐标查找能够接收这个事件的子View
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历所有的child
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 寻找可接收这个事件,且组件区域内包含点击坐标的子View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 找到了可以接收事件的子组件,赋值给newTouchTarget
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
/*把事件传递给child处理,并且 子View消费了这个ACTION_DOWN的touch事件,就把子View添加到TouchTarget链表的最前面;反之,如果子View没有消费这个touch事件(即子View的dispatchTouchEvent返回false),那么子View就不会添加到TouchTarget中,后续的Touch事件也都不会再分发给该子View了*/
mLastTouchDownTime = ev.getDownTime();
......
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//将child添加到TouchTarget链表中(在表头插入),并为mFirstTouchTarget赋值为newTouchTarget,此子View成为新的touch事件的起点
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;//找到一个target之后就跳出循环
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
......
}
}
/* 到目前为止,在 (不拦截 && 不取消 && 是 DOWN 事件) 的前提下,已经在子 View 中寻找过一次事件的响应者。 如果有子 View 消费了事件,那么事件已经通过 dispatchTransformedTouchEvent 方法分发到了该子 View 中,并且 alreadyDispatchedToNewTouchTarget = true,并且将响应者记录在局部变量 newTouchTarget 和 成员变量 mFirstTouchTarget 链表中。*/
/* 如果子View无法消费该touch事件,或者需要拦截touch事件(如果ACTION_DOWN被拦截,那么mFirstTouchTarget =null,后续的touch事件都需要拦截)。这里就作为普通的View处理,调用View.dispatchTouchEvent处理(间接触发onTouchEvent方法)*/
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//两种情形会进入else(1.非ACTION_DOWN事件;2.不需要拦截,且为ACTION_DOWN--这种情形在上面已经处理过,下面需要判断一下,避免重复分发该事件)
//mFirstTouchTarget!=null,找到了能够消费touch事件的子组件,将touch事件传递到子View来处理
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
/*如果前面利用ACTION_DOWN事件寻找符合接收条件的子View的同时消费掉了ACTION_DOWN事件,这里直接返回true ,这里不会重复分发该事件 --上面提到的第2种情形 */
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
/*处理上面提到的第1种情形:不是ACTION_DOWN事件,继续传递给target子View进行处理,符合某些条件的话,会传递ACTION_CANCEL给target子组件
判断条件是:如果ACTION_DOWN时没有被拦截(这时候mFirstTouchTarget !=null,已经找到了事件的接收者),而后面的touch事件被拦截(intercepted为true),则需要发送ACTION_CANCEL给target子组件,反之,直接把该事件传递给子View,这里也可以把ATION_CANCEL拦截下来*/
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 对于非ACTION_DOWN事件,则继续传递给目标子组件进行处理
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
/*/如果ACTION_DOWN没有拦截,但是后续事件被拦截了,将ACTION_CANCEL传递给子View后,还需要把子View从TouchTarget链表中删除,后续的Touch事件将不会再分发给这个子View,这步之后TouchTarget最后为空表,mFirstTouchTarget(TouchTarget第一个节点)为null,后续的Touch事件全部由当前ViewGroup处理*/
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 如果是ACTION_CANCEL或者ACTION_UP,重置Touch状态标识,mFirstTouchTarget赋值为null,后面的Touch事件都无法派发给子View
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
这里希望大家仔细阅读一下上面对源码的注释,通过这些注释可以很好的帮助我们理解ViewGroup的事件分发逻辑,几乎所有与ViewGroup事件分发相关的结论都可以从上面的注释中找到根据。
如果大家有仔细读过上面的代码和注释,就会发现ViewGroup 的 dispatchTouchEvent 处理逻辑主要分为以下 4个步骤:
1)如果是 DOWN 事件,则清理之前的变量和状态
2)检查是否拦截该Touch事件
3)分发 DOWN 事件或其他初始事件
4)将事件分发到 touchTarget 中或分发到ViewGroup自己身上。
mFirstTouchTarget 变量在什么时候赋值,它的作用是什么?
mFirstTouchTarget 是用来记录在 DOWN 事件中消费了事件的子 View,它以链表的形式保存,mFirstTouchTarget指向链表的第一个节点。在 DOWN 事件中,如果通过点击的坐标找到了某个子 View,且该子 View 消费了事件,那么就将这个子 View 插入到TouchTarget链表中,具体地,在调用 addTouchTarget(child, idBitsToAssign)的时候赋值。这样在后续的 MOVE、UP 事件中,能直接根据这个链表,将事件分发给目标子 View,不需再重复遍历子 View 去查找事件的接收者。
ViewGroup 在哪些情况下可以拦截事件?
是否拦截是由 onInterceptTouchEvent 方法的返回值决定的。假设该 ViewGroup 没有被设置为不允许拦截(即默认情况下),那么对于 DOWN 事件,onInterceptTouchEvent 方法肯定会被调用。如果是 ACTION_MOVE、ACTION_UP 或其他事件类型,只要满足 mFirstTouchTarget != null 时也会调用 onInterceptTouchEvent。
onInterceptTouchEvent 方法对不同类型的事件进行拦截,会有哪些影响?
对分发的事件进行拦截,注意拦截ACION_DOWN与其他ACTION的差异。这里分两种情况讨论:
1)如果ACTION_DOWN的事件没有被拦截,顺利找到了TargetView,那么后续的MOVE与UP都能够下发。如果后续的MOVE与UP下发时还有继续拦截的话,事件只能传递到拦截层,并且发出ACTION_CANCEL给子View处理(不会传递到当前View(或Activity)的onTouchEvent),再后面的Touch事件也都不会再分发给子View了,极端情况mFirstTouchTarget为null,所有事件都由当前的ViewGroup的onTouchEvent来处理。
2)如果ACITON_DOWN的事件下发时被拦截,导致没有找到TargetView,那么后续的MOVE与UP都无法向下派发了,在当前View(也可能是Activity)这一层就终止了传递,直接由当前View的onTouchEvent来处理。后续的 MOVE、UP 事件也不会再询问是否需要拦截,而是直接分发到当前ViewGroup的 onTouchEvent 方法去处理。
因此,DOWN 事件在 ViewGroup 的事件拦截、分发过程中是一个特殊的角色,对其处理的结果将直接影响后续事件的分发流程。
四、Activity 中 Touch 事件的分发逻辑
了解完 View 和 ViewGroup 的事件分发逻辑后,再来看 Activity 的分发逻辑就简单多了。
看 Activity.dispatchTouchEvent 的源码:
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
在Actvity的dispatchTouchEvent中,先尝试调用 window.superDispatchTouchEvent 方法,返回 false 时才调用 onTouchEvent 方法。而 window.superDispatchTouchEvent 方法,实际上是调用了 Window 的 DecorView 的 dispatchTouchEvent 方法,由于 DecorView 是 FrameLayout 的子类,也就是一个 ViewGroup,所以 Activity.dispatchTouchEvent 方法最终也是调用了 ViewGroup.dispatchTouchEvent 方法。
至此为止,我们将 View、ViewGroup、Activity 的事件分发流程都了解完了。可以想象,当用户触发了一个触摸事件,Android 系统会将其传递到当前触摸的 Activity.dispatchTouchEvent 方法中,接着,就由 Activity、ViewGroup、View 的 dispatchTouchEvent 方法把事件传递给某个目标 View,然后再逐层返回。
五、例子
最后,我们再通过一个例子来回顾一下整个分发过程。
下面的例子中有一个Activity,在Activity中有一个MyViewGroup,在MyViewGroup中有一个MyButton。然后模拟不同的事件分发情形,并观察打印的log来验证上文对源码中Touch事件分发机制的分析。下面贴出部分代码
Activity源码:
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatchTouchEvent:ACTION_DOWN");
// return true;
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatchTouchEvent:ACTION_MOVE");
break;
// break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatchTouchEvent:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.i(TAG, "dispatchTouchEvent:ACTION_CANCEL");
break;
default:
Log.i(TAG, "dispatchTouchEvent:other");
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onTouchEvent:ACTION_DOWN");
// return true;
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onTouchEvent:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "onTouchEvent:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.i(TAG, "onTouchEventt:ACTION_CANCEL");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}
MyViewGroup源码:
public class MyViewGroup extends FrameLayout {
private static final String TAG = MyViewGroup.class.getSimpleName();
......
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatchTouchEvent:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatchTouchEvent:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatchTouchEvent:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.i(TAG, "dispatchTouchEvent:ACTION_CANCEL");
break;
default:
Log.i(TAG, "dispatchTouchEvent:other");
}
//return true;
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onTouchEvent:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onTouchEvent:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "onTouchEvent:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.i(TAG, "onTouchEventt:ACTION_CANCEL");
break;
default:
Log.i(TAG, "dispatchTouchEvent:other");
}
//return true;
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onInterceptTouchEvent:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onInterceptTouchEvent:ACTION_MOVE");
break;
// return true;
case MotionEvent.ACTION_UP:
Log.i(TAG, "onInterceptTouchEvent:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.i(TAG, "onInterceptTouchEvent:ACTION_CANCEL");
break;
default:
break;
}
return false;
// return super.onInterceptTouchEvent(ev);
}
}
R.layout.activity_main布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<com.example.toucheventdispatchsample.MyViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.toucheventdispatchsample.MyButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="myButton"/>
</com.example.toucheventdispatchsample.MyViewGroup>
</LinearLayout>
下面通过修改dispatchTouchEvent,oninterceptTouchEvent,onTouchEvent的返回值,来模拟几个典型的情形:
1)MyButton消费所有事件,MyViewGroup不拦截任何事件
2)在MyButton的onTouchEvent 中返回false,MyViewGroup也不消费任何事件,后续的touch事件直接由Activity处理了
3)MyViewGroup拦截了ACTION_DOWN事件,但是不消费任何事件,因此ACTION_DOWN由MyViewGroup处理,由于MyViewGroup并没有消费任何事件(onTouchEvent中返回了false),所以后续事件直接交给Activity处理
4)ViewGroup拦截ACTION_MOVE,且在自己的onTouchEvent 中针对ACTION_MOVE和ACTION_UP返回true,MyButton接收到ACTION_CANCEL后,后续的touch事件由MyViewGroup处理
5)MyViewGroup在dispatchTouchEvent中直接返回true,那么Touch事件将不会任何被处理。在dispatchTouchEvent中返回true,上层的Activity就会任何Touch事件已经被下层的某个View处理了,但实际上并不会调用onTouchEvent。
6)在MyButton的onTouchEvent 中遇到ACTION_DOWN返回true,但是在后续的ACTION_MOVE返回false,MyButton仍然可以收到后续的事件(ACTION_MOVE或ACTION_DOWN),同时Activity也会收到这些后续事件。
这里因为在MyButton的onTouchEvent中ACTION_UP中返回了true,所以Activity的onTouchEvent没有收到ACTION_UP。
到此为止,针对Android中Touch事件的分发机制的分析就结束了。理解有误的地方,欢迎讨论。