V ie w 内默认消息派发过程
public boolean dispatchTouchEvent(MotionEvent event) {
/**
* 一 调 用 onFilterTouchEventForSecurity()处理窗口处于模糊显示状态下的消息。所谓的模糊显示是
* 指 ,应用程序可以设置当前窗口为模糊状态,此时窗口内部的所有视图将显示为模糊效果。这样做的目
* 的 是 为 了 隐 藏 窗 口 中 的 内 容 , 对 于 其 中 各 个 视 图 而 言 , 可 以 设 置 该 视 图 的
* HLTER_TOUCHES__WHEN_OBSCURED标识,如存在该标识,则意味着用户希望不要处理该消息。
*/
if (!onFilterTouchEventForSecurity(event)) {
return false;
}
/**
* 二 、回调视图监听者的onTouchO函数,如果监听者消耗了该消息,则直接返回
*/
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
/**
* 调 用 onTouchEventO,应用程序可以重载该函数,但如果没有重载的话,该函数内部有默认的
* 执行方式。
*/
return onTouchEvent(event);
}
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
/**
* 一 、判断该视图是否为disable状态,如果是,什么都不干,返回true,即消耗该消息。
*/
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
/**
* 二 处理消息代理TouchDelegate。所谓的消息代理是指,可以给某个View指定一个消息处理代
* 理,当 View收到消息时,首先将该消息派发给其代理进行处理。如果代理内部消耗了该消息,则 View
* 不需要再进行任何处理;如果代理没有处理,则 View继续按照默认的逻辑进行处理。源码中该类的注
* 释中说,该类的目的是为了扩大点击区,意思很简单,
* 在一般情况下,只有当用户点击到该区域时,该 View
* 对象才能处理Touch消息。然而有时却希望实际的点击区域能够大于View本身的区域,如虚线区所示,
* 这种情况一般发生在该View本身周围没有其他视图时,为了提高点击的准确率故意设置。这个想法是
* 好的,但是源码中却没有真正实现这个目的,因为要实现这个目的,必须在将消息匹配到对应窗口的判
* 断中使用代理视图的大小,而不能使用视图本身的大小,否则,该视图将因为点击位置没有落到视图区
* 而被忽略。然而源码中却没有经过这个判断,所 以 TouchDelegate形同虚设。
*/
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
/**
* 判断该视图是否是可以点击的,如果不可点击,则直接返回false,即不处理该消息。否则,
* 真正开始执行触摸消息的默认处理逻辑,该逻辑中分别处理了 ACTION4DOWN、 MOVE和 UP消息,
* 具体过程如下:
* 1)在 ACTION_DOWN消息中给 mPrivateFlags变 量 添 加 PRESSED标 识 ,并将变量
* mHasPerformLongPress置 为 false,然 后 启 动 ta p 监 测 , 即 发 送 一 个异
* 步 延 迟 消 息 ,延迟时间为
* ViewConfigration.getTapTimeout()。
*
* 2)对于触摸消息而言,消息本身没有repeat的属性,这与按键消息有所不同,一次触摸消息只有
* 一 个DOWN消息,接下来就是连续的MOVE消息,并最终以UP消息结束。因此,本步骤中对MOVE
* 消息进行处理。具体逻辑包括,判断是否移动到了视图区以外,如果是,则删除 tap或 者 longPress的
* 监测,并清除mPrivateFlags中的PRESSED标识,然后调用refreshDrawableState()刷新视图的状态,这
* 将导致对背景根据状态进行重绘。
*
* 这个处理逻辑有点问题。在一般的GUI系统中,比如HTC magic手机,或者苹果系统,或者Windows
* 系统,当用户按下某个按钮时,如果移动光标到按钮外,此时按钮会从高亮恢复到普通状态,但如果用
* 户把光标再移回到该按钮上时,按钮又会重新变为高亮。然而在目前的Android系统中,代码的设计是,
* 当用户把光标再次移回到视图区时,视图不会重新获得聚焦,实际测试结果也是如此。
* 3) 处 理ACTION_UP消息,代码中判断该UP消息是发生在前面所讲的哪一个监测时间段中,并据
* 此进行不同的处理。
* a .查看是否发生在pre-pressed时间段内,如果是,则给变量prepressed赋 值为true。
* b .无论是发生在pre-pressed区还是发生在press区,都应该让该视图获取焦点,前提是该视图在
* Touch模式下可以拥有焦点。
* c .如果发生在press之后,即长按,则什么都不做,因为长按的异步消息处理中已经处理了长按消
* 息 。 如 果 是 发 生 在 长 按 之 前 , 则 变 量 mHasPerformedLongPress为 false, 此 时 调 用
* removeLongPressCallbackO移除还没有执行的长按监测消息。
* d .判断变量focusTaken的局部变量是否为false,在一般情况下,该变量都是false, 因为一般情况
* 下视图默认在Touch模式下是不能获得焦点的,所 以 requestFocus()会 返 回false。当然,只有当返回false
* 的情况下,该 UP消息才能导致回调performClick()函数,否则,当 focusTaken为 true时,用户点击某
* 个视图,该视图仅仅是获得焦点,必须再点击一次才能执行performClick()函数。因为再点击一次时,
* 该视图已经获得了焦点,从而不会调用requestFocus(),所以变量focusTaken为初始值false。该步说明
* 了 tap和 press动作的相同及区别,相同的地方在于它们都会引起视图聚焦或者执行performClick(),区
* 别在 于tap不会改变视图的状态,而 press会把视图的状态改变为PRESSED。从功能的角度来看,两者
* 没有本质区别,仅仅是反映的U I效果有少许差别而已。
* 5、分别处理tap和 press动作。如 果 是press动作,则 清 除PRESSED标识,并改变视图状态;
* 如果是tap,仅仅发送一个UnSetPressedState异步消息。
*/
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
/**
* 调用 removeTapCallBack()关闭tap检测
*/
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
/**
* 处 理 ACTION—CANCEL消息,这里只需要清除PRESSED标识,刷新视图状态,然后再关闭
* tap监测即可。
*/
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}