事件分发系列—View中的dispatchTouchEvent和onTouchEvent分析
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)了屏幕
下图是触摸消息随时间变化的时间轴示意图:
相关引用来自> http://www.linuxidc.com/Linux/2012-08/67979.htm
了解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的回调。
更多相关文章
- listview中放Button 点击 长按事件
- Android事件分发机制解析
- 扩大View的点击区域
- Android事件处理(6)
- Android(安卓)GridView宫格视图(一) 运用--BaseAdapter
- Android(安卓)Activity 生命周期
- 【翻译】Android(安卓)Support Library Features(二)
- Android状态栏透明(沉浸式效果)
- Android(安卓)EditText回车不换行