Android事件分发05——View的onTouchEvent
2017-04-20 13:17
411 查看
Android事件分发05View的onTouchEvent
一onTouchEvent的源码
二动作处理之前
三按下的判断
四MotionEventACTION_DOWN
五MotionEventACTION_MOVE
六MotionEventACTION_UP
七MotionEventACTION_CANCEL
八流程图
这里面首先判断空寂是否不可用,不可用的情况下,我们还是会消费消费事件的,只是不响应而已。
如果不满足上面的情况,我们接着判断这个控件有没有添加了触摸的代理,如果添加了,那么我们直接调用代理的onTouchEvent方法,如果这个方法消费了事件,那么直接放回true。负责继续往下走,走入到action处理的核心。
这里面判断是否可以点击,只有可以点击才处理,进入后,处理完成直接返回true,代表消费了。
4-6行:这里面根据performButtonActionOnTouchDown方法的返回值判断是否对down动作还要处理,那么我们看看performButtonActionOnTouchDown方法
这个方法处理的是,鼠标的动作。是鼠标,并且点击了右键,才返回true。因此我们可以知道我们这里面放回的false。
第10行:
这里面的处理,其实就是获取不断的获取父控件,判断父控件是否具有滚动的功能。
15–23行:如果这个控件包含在可滚动的控件内,我们需要延迟反馈。延迟100毫秒,后执行 mPendingCheckForTap 任务。
ViewConfiguration
View
这里面我们来看看checkForLongClick
这里面我们看看
CheckForLongPress
现在到了 performLongClick
23–28行:如果没有被包含在可滚动的控件中,那么直接显示反馈
总结按下的处理:其实就是被包含在滚动控件中,那么延时按下的返回,反之,立即反馈。
这里面的操作,其实就是,如果点没有落在view上,那么取消我们的处理。负责不操作。
pointInView在以后的文章中分析
这里面注释已经很详细了。主要看看一哈22–40行:
22行:mHasPerformedLongPress 这个参数,我们在LongPress的延迟处理中赋过值(CheckForLongPress ),也即是说,如果发生了长按这个值为true。
33–35行:这里面涉及到了PerformClick这个类,我们来看看
PerformClick
这个看到这个里面直接执行performClick()
performClick()
performClick方法里面我们可以看到,主要就是判断我们有没有添加监听,如果添加了监听,调用监听的onClick(
36–38行:
这里if判断,主要是判断这个任务有没有添加到消息队列中去了,如果没有添加成功,那么直接调用performClick。我们可以看看 post方法
方法上说的很清楚了:如果Runnable成功添加到消息队列中返回true。失败返回false。
小小的总结:我们对up动作小小的总结一下,主要就是判断有没有按下,然后判断要不要执行onClick方法。
这个动作中,其实就是恢复一些初始化状态。
一onTouchEvent的源码
二动作处理之前
三按下的判断
四MotionEventACTION_DOWN
五MotionEventACTION_MOVE
六MotionEventACTION_UP
七MotionEventACTION_CANCEL
八流程图
Android事件分发05——View的onTouchEvent
上次我们分析了view的dispatchTouchEvent(Android事件分发04——View的dispatchTouchEvent ),说到事件分发到view的dispatchTouchEvent后,会先到触摸监听的onTouch中,然后再到view的onTouchEvent中。现在我们一起来看看onTouchEvent一、onTouchEvent的源码
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); //判断控件是否不可用 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // 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) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } //如果设置了Touch的代理,那么直接调用代理的 onTouchEvent,如下消费了,那么返回true if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //控件可点击的(包括点击、长按等)才操作,操作完成,直接返回true表示消费了 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: //获取是否具有 短暂延反馈的情况,就是down中包含在滚动控件中的情况,这个就是去除了滚动的情况 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_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 (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. setPressed(true, x, y); } //没有长按并且mIgnoreNextUpEvent==false if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // 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) {//如果按下了 //添加一个延时任务,这个任务把按下标记设为false postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now //任务调用失败,直接执行这个任务 mUnsetPressedState.run(); } //移除敲击事件 removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. //获取控件是否包含在可滚动的view中 boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. //如果控件包含在可滚动的控件中,以防滚动,这里需要延迟按下的反馈 if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away //如果没包含,那么立即显示反馈 setPressed(true, x, y); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false);//按下设为false removeTapCallback();//取消敲击 removeLongPressCallback();//取消长按 mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_MOVE: //状态改变的处理和分发 drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons //移动的点没在控件内 if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback();//移除敲击回调 if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback();//移除长按 setPressed(false);//按下设置为空 } } break; } return true; } return false; }
二、动作处理之前
动作处理之前 : 2—25行final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); //判断控件是否不可用 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // 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) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } //如果设置了Touch的代理,那么直接调用代理的 onTouchEvent,如下消费了,那么返回true if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } }
这里面首先判断空寂是否不可用,不可用的情况下,我们还是会消费消费事件的,只是不响应而已。
如果不满足上面的情况,我们接着判断这个控件有没有添加了触摸的代理,如果添加了,那么我们直接调用代理的onTouchEvent方法,如果这个方法消费了事件,那么直接放回true。负责继续往下走,走入到action处理的核心。
三、按下的判断
对应:28–151行//控件可点击的(包括点击、长按等)才操作,操作完成,直接返回true表示消费了 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { .... return true; }
这里面判断是否可以点击,只有可以点击才处理,进入后,处理完成直接返回true,代表消费了。
四、MotionEvent.ACTION_DOWN
case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. //获取控件是否包含在可滚动的view中 boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. //如果控件包含在可滚动的控件中,以防滚动,这里需要延迟按下的反馈 if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away //如果没包含,那么立即显示反馈 setPressed(true, x, y); checkForLongClick(0); } break;
4-6行:这里面根据performButtonActionOnTouchDown方法的返回值判断是否对down动作还要处理,那么我们看看performButtonActionOnTouchDown方法
protected boolean performButtonActionOnTouchDown(MotionEvent event) { if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE && (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) { showContextMenu(event.getX(), event.getY(), event.getMetaState()); mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT; return true; } return false; }
这个方法处理的是,鼠标的动作。是鼠标,并且点击了右键,才返回true。因此我们可以知道我们这里面放回的false。
第10行:
boolean isInScrollingContainer = isInScrollingContainer();调用了 isInScrollingContainer()方法
public boolean isInScrollingContainer() { ViewParent p = getParent(); while (p != null && p instanceof ViewGroup) { if (((ViewGroup) p).shouldDelayChildPressedState()) { return true; } p = p.getParent(); } return false; }
这里面的处理,其实就是获取不断的获取父控件,判断父控件是否具有滚动的功能。
/** * Return true if the pressed state should be delayed for children or descendants of this * ViewGroup. Generally, this should be done for containers that can scroll, such as a List. * This prevents the pressed state from appearing when the user is actually trying to scroll * the content. * * The default implementation returns true for compatibility reasons. Subclasses that do * not scroll should generally override this method and return false. */ public boolean shouldDelayChildPressedState() { return true; }
15–23行:如果这个控件包含在可滚动的控件内,我们需要延迟反馈。延迟100毫秒,后执行 mPendingCheckForTap 任务。
ViewConfiguration
private static final int TAP_TIMEOUT = 100;//毫秒 public static int getTapTimeout() { return TAP_TIMEOUT; }
View
private final class CheckForTap implements Runnable { public float x; public float y; @Override public void run() { mPrivateFlags &= ~PFLAG_PREPRESSED; setPressed(true, x, y); checkForLongClick(ViewConfiguration.getTapTimeout()); } }
这里面我们来看看checkForLongClick
private void checkForLongClick(int delayOffset) { //是否具有长按标记 if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } //编辑window添加的次数 mPendingCheckForLongPress.rememberWindowAttachCount(); //延时执行任务 500-100=400ms postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); } }
这里面我们看看
CheckForLongPress
private final class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; @Override public void run() { //是按下的,父控件部位空,自己标记的和view的添加的次数相同 if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick()) { mHasPerformedLongPress = true; } } } public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } }
现在到了 performLongClick
public boolean performLongClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; ListenerInfo li = mListenerInfo; //监听不为空,使用监听来处理事件 if (li != null && li.mOnLongClickListener != null) { handled = li.mOnLongClickListener.onLongClick(View.this); } //如果没有消费事件,使用showContextMenu来处理 if (!handled) { handled = showContextMenu(); } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return handled; }
23–28行:如果没有被包含在可滚动的控件中,那么直接显示反馈
总结按下的处理:其实就是被包含在滚动控件中,那么延时按下的返回,反之,立即反馈。
五、MotionEvent.ACTION_MOVE
case MotionEvent.ACTION_MOVE: //状态改变的处理和分发 drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons //移动的点没在控件内 if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback();//移除敲击回调 if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback();//移除长按 setPressed(false);//按下设置为空 } } break;
这里面的操作,其实就是,如果点没有落在view上,那么取消我们的处理。负责不操作。
pointInView在以后的文章中分析
六、MotionEvent.ACTION_UP
case MotionEvent.ACTION_UP: //获取是否具有 短暂延反馈的情况,就是down中包含在滚动控件中的情况,这个就是去除了滚动的情况 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_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 (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. setPressed(true, x, y); } //没有长按并且mIgnoreNextUpEvent==false if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // 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) {//如果按下了 //添加一个延时任务,这个任务把按下标记设为false postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now //任务调用失败,直接执行这个任务 mUnsetPressedState.run(); } //移除敲击事件 removeTapCallback(); } mIgnoreNextUpEvent = false; break;
这里面注释已经很详细了。主要看看一哈22–40行:
22行:mHasPerformedLongPress 这个参数,我们在LongPress的延迟处理中赋过值(CheckForLongPress ),也即是说,如果发生了长按这个值为true。
33–35行:这里面涉及到了PerformClick这个类,我们来看看
PerformClick
private final class PerformClick implements Runnable { @Override public void run() { performClick(); } }
这个看到这个里面直接执行performClick()
performClick()
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
performClick方法里面我们可以看到,主要就是判断我们有没有添加监听,如果添加了监听,调用监听的onClick(
li.mOnClickListener.onClick(this);)并且返回true,代表消费了事件,否则返回false。
36–38行:
这里if判断,主要是判断这个任务有没有添加到消息队列中去了,如果没有添加成功,那么直接调用performClick。我们可以看看 post方法
/** * <p>Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.</p> * * @param action The Runnable that will be executed. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. * * @see #postDelayed * @see #removeCallbacks */ public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; }
方法上说的很清楚了:如果Runnable成功添加到消息队列中返回true。失败返回false。
小小的总结:我们对up动作小小的总结一下,主要就是判断有没有按下,然后判断要不要执行onClick方法。
七、MotionEvent.ACTION_CANCEL
case MotionEvent.ACTION_CANCEL: setPressed(false);//按下设为false removeTapCallback();//取消敲击 removeLongPressCallback();//取消长按 mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break;
这个动作中,其实就是恢复一些初始化状态。
八、流程图
通过上面的分析,我们来个流程图总结一下。相关文章推荐
- Android dispatchTouchEvent View事件分发
- Android View框架总结(九)KeyEvent事件分发机制
- android ViewGoup事件分发机制dispatchTouchEvent
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- 从源码角度带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程(一)
- Android 事件分发(dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent)
- ViewGroup触摸事件的分发,拦截,消耗(dispatchTouchEvent , onInterceptTouchEvent , onTouchEvent)
- View触摸事件分发流程dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- Android View框架总结(九)KeyEvent事件分发机制
- 事件分发 细说Android事件传递机制(dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent)
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- Android View框架总结(九)KeyEvent事件分发机制
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- Android事件分发04——View的dispatchTouchEvent
- Android中view的onTouch&onClick事件分发机制详解
- Android事件分发03——ViewGrop的dispatchTouchEvent
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- android 事件分发 点击事件 ondispathTouchEvent onTouchEvent onInterceptTouchEvent
- Android 触屏事件 OnTouch onClick onTouchEvent对于触屏事件的处理和分发