dispatchTouchEvent
话不多说直接上源码
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
* 将屏幕的按压事件传递给目标view,或者当前view即目标view
*
* @param event The motion event to be dispatched.
* 需要分发的事件
*
* @return True if the event was handled by the view, false otherwise.
* 如果返回true表示这个事件被这个view处理了,否则反
*/
public boolean dispatchTouchEvent(MotionEvent event) {
//系统调试分析相关,没有影响
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//过滤是不是能够传递这个touch事件
if (onFilterTouchEventForSecurity(event)) {
//首先判断我们在使用该view的时候是否有实现OnTouchListener,如果有实现就判断当前的view
//状态是不是ENABLED,如果实现的OnTouchListener的onTouch中返回true,并处理事件,则
//返回true,这个事件在此处理了。
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
//如果没有在代码里面setOnTouchListener的话,就判断View自身的onTouchEvent方法有没有
//处理,没有处理最后返回false,处理了返回true;
if (onTouchEvent(event)) {
return true;
}
}
//系统调试分析相关,没有影响
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
//如果即没有setOnTouchListener,也没有在onTouchEvent中处理,就返回false
return false;
}
从上面的源码可以看出:在View的分发事件方法dispatchTouchEvent中,处理分发的顺序是实现OnTouchListener的onTouch(),之后是当前View的onTouchEvent(event)方法。
onTouchEvent
onTouchEvent的源码有点长,所以要沉下心来仔细阅读。事件消耗和事件处理都是返回true,事件消耗就相当于占坑不拉屎,虽然有点恶心哈,事件处理当然就是占坑拉屎。
在Android的触摸消息中,已经实现了三种监测,它们分别是
1)pre-pressed:对应的语义是用户轻触(tap)了屏幕
2)pressed:对应的语义是用户点击(press)了屏幕
3)long pressed:对应的语义是用户长按(long press)了屏幕
下图是触摸消息随时间变化的时间轴示意图:
了解onTouchEvent就现需要了解的方法和类
- CheckForTap类
该类实现了Runnable接口,在run函数中设置触摸标识,并刷新Drawable的状态,同时用于发送一个检测长按事件的异步延迟消息,代码如下:
private final class CheckForTap implements Runnable {
public void run() {
// 进入该函数,说明已经过了ViewConfiguration.getTapTimeout()时间,
// 即pre-pressed状态结束,宣告触摸进入pressed状态
mPrivateFlags &= ~PREPRESSED;
mPrivateFlags |= PRESSED;
refreshDrawableState(); // 刷新控件的背景Drawable
// 如果长按检测没有被去使能,则发送一个检测长按事件的异步延迟消息
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick(ViewConfiguration.getTapTimeout());
}
}
}
private void postCheckForLongClick(int delayOffset) {
mHasPerformedLongPress = false;
// 实例化CheckForLongPress对象
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
// 调用PostDelayed函数发送长按事件的异步延迟消息
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
- CheckForLongPress类
该类定义了长按操作发生时的响应处理,同样实现了Runnable接口
class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
public void run() {
// 进入该函数,说明检测到了长按操作
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
if (mOnLongClickListener != null) {
// 回调用户实现的长按操作监听函数(OnLongClickListener)
handled = mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
// 如果OnLongClickListener的onLongClick返回false
// 则需要继续处理该长按事件,这里是显示上下文菜单
handled = showContextMenu();
}
if (handled) {
// 长按操作事件被处理了,此时应该给用户触觉上的反馈
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
了解完,我们来看看onTouchEvent的实现
/**
* Implement this method to handle touch screen motion events.
* 如果需要处理屏幕产生的事件流需要实现这个方法
*
* @param event The motion event.
*
* @return True if the event was handled, false otherwise.
* 如果返回true表示这个处理了这个事件,false则反
*/
public boolean onTouchEvent(MotionEvent event) {
//viewFLags用来记录当前View的状态
final int viewFlags = mViewFlags;
//如果当前View状态为DISABLED,如果不清楚DISABLED是一种什么状态那你应该用过
//setEnabled(boolean enabled)这个方法,DISABLED就是ENABLED相反的状态。
//DISABLED = 0x00000020 ,ENABLED_MASK = 0x00000020
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
//如果View的状态是被按压过,且当抬起事件产生,重置View状态为未按压,刷新Drawable的状态
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
//如果当前View是一个DISABLED状态,且当前View是一个可点击或者是可长按的状态则当前事件在
//此消耗不做处理,返回true。
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
----------
//TouchDelegate是一个事件处理逻辑封装的一个类,也就是说Touch事件处理被委托了,那么就交由
//mTouchDelegate.onTouchEvent处理,如果返回true,则事件被处理了,则不会向下传递
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
----------
//如果当前View的状态是可点击或者是可长按的,就对事件流进行细节处理
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
//PREPRESSED = 0x02000000
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
//如果是pressed状态或者是prepressed状态,才进行处理
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don‘t have it already and we should in
// touch mode.
//如果设定了获取焦点,那么调用requestFocus获得焦点
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
//在释放之前给用户显示View的prepressed的状态,状态需要改变为
//PRESSED,并且需要将背景变为按下的状态为了让用户感知到
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
// 是否处理过长按操作了,如果是,则直接返回
if (!mHasPerformedLongPress) {
//如果不是长按的话,仅仅是一个Tap,所以移除长按的回调
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.
//UI子线程去执行click,为了让click事件开始的时候其他视觉发
//生变化不影响。
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//如果post消息失败,直接调用处理click事件
if (!post(mPerformClick)) {
performClick();
}
}
}
----------
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
//ViewConfiguration.getPressedStateDuration() 获得的是按下效
//果显示的时间,由PRESSED_STATE_DURATION常量指定,在2.2中为125
//毫秒,也就是隔了125毫秒按钮的状态重置为未点击之前的状态。目的是让用户
//感知到click的效果
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
//如果通过post(Runnable runnable)方式调用失败,则直接调用
mUnsetPressedState.run();
}
//移除Tap的回调 重置View的状态
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
//在触摸事件中执行按钮相关的动作,如果返回true则表示已经消耗了down
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we‘re inside a scrolling container.
//判断是否在一个滚动的容器内
boolean isInScrollingContainer = isInScrollingContainer();
// 如果父容器是一个可滚动的容器
if (isInScrollingContainer) {
mPrivateFlags |= PREPRESSED;
//将view的状态变为PREPRESSED,检测是Tap还是长按事件
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
//直接将view状态转化为PRESSED,更新Drawable
mPrivateFlags |= PRESSED;
refreshDrawableState();
//是否是长按事件的判断
checkForLongClick(0);
}
break;
//接收到系统发出的ACTION_CANCLE事件时,重置状态
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();
// 如果移动超出了按钮的范围
if (!pointInView(x, y, mTouchSlop)) {
// 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;
}
对于onTouchEvent总的来说,首先受到事件首先判断当前View的状态是否为DISABLED,如果是则只需要简单的做一些状态的重置,不对事件做细节处理。如果不是DISABLED,就需要对事件细节进行处理,这时候又半路来个程咬金TouchDelegate,如果mTouchDelegate不为空,且返回了true,就表示该事件流有人给你代劳处理了,后面的分析View自己也不用做了。最后如果没人拦截处理,那就得View自己来。
下面开始是View自己处理事件流的逻辑过程描叙,即switch判断事件分支的处理:
- ACTION_DOWN
判断是否在触摸事件中执行按钮相关的动作,如果是,直接跳出,不是则继续判断当前View是否在一个可滑动的容器中,如果是则判断是否是一个点击tab事件,还是长按事件的检查,如果不是则直接转化状态为PRESSED并判断是否为长按事件。
- ACTION_MOVE
判断移动的点是否在当前View中,如果不在其中且当前状态为PRESSED则重置非PRESSED,且移除长按的回调。
- ACTION_UP
当抬起事件产生,首先判断View的状态是pressed状态或者是prepressed状态(也就是按过),才进行处理,如果是prepressed状态则变为pressed,并更新Drawable,然后判断在Down中的mHasPerformedLongPress标志,有没有变为true,也就是有没有产生执行长按事件,如果没有,则把这个事件流当做一个click事件做处理,也就是执行performClick中的代码,执行完click中的代码,最后重置View的状态和刷新Drawable。
- ACTION_CANCLE
当系统发送一个action_cancle的事件,则重置View的状态为非PRESSED,刷新Drawable的状态,且移除Tab的回调。