/** * 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. */ publicbooleandispatchTouchEvent(MotionEvent event){ //最前面这一段就是判断当前事件是否能获得焦点,如果不能获得焦点或者不存在一个View,那我们就直接返回False跳出循环 // If the event should be handled by accessibility focus first. // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { returnfalse; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } //设置返回的默认值 boolean result = false;
//这段是系统调试方面,可以直接忽略 if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); }
//Android用一个32位的整型值表示一次TouchEvent事件,低8位表示touch事件的具体动作,比如按下,抬起,滑动,还有多点触控时的按下,抬起,这个和单点是区分开的,下面看具体的方法: //1 getAction:触摸动作的原始32位信息,包括事件的动作,触控点信息 //2 getActionMasked:触摸的动作,按下,抬起,滑动,多点按下,多点抬起 //3 getActionIndex:触控点信息 finalint actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture // 当我们手指按到View上时,其他的依赖滑动都要先停下 stopNestedScroll(); }
//过滤掉一些不合法的事件,比如当前的View的窗口被遮挡了。 if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //ListenerInfo 是view的一个内部类 里面有各种各样的listener,例如OnClickListener,OnLongClickListener,OnTouchListener等等 //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; //首先判断如果监听li对象!=null 且我们通过setOnTouchListener设置了监听,即是否有实现OnTouchListener,如果有实现就判断当前的view状态是不是ENABLED,如果实现的OnTouchListener的onTouch中返回true,并处理事件 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { //如果满足这些条件那么返回true,这个事件就在此处理 // 意味着这个View需要事件分发 result = true; }
//如果上一段判断的条件没有满足(没有在代码里面setOnTouchListener的话),就判断View自身的onTouchEvent方法有没有处理,没有处理最后返回false,处理了返回true; if (!result && onTouchEvent(event)) { result = true; } } //系统调试分析相关,没有影响 if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); }
// Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. //如果这是手势的结尾,则在嵌套滚动后清理 if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); }
return result; }
① 不得不说,dispatchTouchEvent 的源码 比之前郭霖大佬那时候分析的源码改变了太多。
② 源码看起来似乎很多,但是我们只需要关注下面的代码即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
//首先判断如果监听li对象!=null 且我们通过setOnTouchListener设置了监听,即是否有实现OnTouchListener,如果有实现就判断当前的view状态是不是ENABLED,如果实现的OnTouchListener的onTouch中返回true,并处理事件 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { //如果满足这些条件那么返回true,这个事件就在此处理 // 意味着这个View需要事件分发 result = true; }
//如果上一段判断的条件没有满足(没有在代码里面setOnTouchListener的话),就判断View自身的onTouchEvent方法有没有处理,没有处理最后返回false,处理了返回true; if (!result && onTouchEvent(event)) { result = true; }
/** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: * <ul> * <li>obeying click sound preferences * <li>dispatching OnClickListener calls * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled * </ul> * * @param event The motion event. * @return True if the event was handled, false otherwise. */ publicbooleanonTouchEvent(MotionEvent event){ // 获取动作点击屏幕的位置坐标 finalfloat x = event.getX(); finalfloat y = event.getY(); finalint viewFlags = mViewFlags; // 获取手势动作 finalint action = event.getAction();
//如果当前View状态为DISABLED if ((viewFlags & ENABLED_MASK) == DISABLED) { //如果View的状态是被按压过,且当抬起事件产生,重置View状态为未按压,刷新Drawable的状态 if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable; } // 如果设置了触摸代理 if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { //就交给mTouchDelegate.onTouchEvent处理,如果返回true,则事件被处理了,则不会向下传递 returntrue; } } //如果当前View的clickable是可点击的,就对事件流进行细节处理 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { //如果是抬起的手势 case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } //清除各种状态 if (!clickable) { removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } //prepressed指的是,如果view包裹在一个scrolling View中,可能会进行滑动处理,所以设置了一个prePress的状态 //大致是等待一定时间,然后没有被父类拦截了事件,则认为是点击到了当前的view,从而显示点击态 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; //如果是pressed状态或者是prepressed状态,才进行处理 if ((mPrivateFlags & PFLAG_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(); }
//在释放之前给用户显示View的prepressed的状态,状态需要改变为PRESSED,并且需要将背景变为按下的状态为了让用户感知到 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); }
// 是否处理过长按操作了,如果是,则直接返回 if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check //如果不是长按的话,仅仅是一个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)) { performClickInternal(); } } }
if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); }
if (prepressed) { //ViewConfiguration.getPressedStateDuration() 获得的是按下效果显示的时间,由PRESSED_STATE_DURATION常量指定,在2.2中为125毫秒,也就是隔了125毫秒按钮的状态重置为未点击之前的状态。目的是让用户感知到click的效果 postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } elseif (!post(mUnsetPressedState)) { // If the post failed, unpress right now //如果通过post(Runnable runnable)方式调用失败,则直接调用 mUnsetPressedState.run(); }
if (!clickable) { //仅在View支持长按时执行有效,否则直接退出方法 checkForLongClick( ViewConfiguration.getLongPressTimeout(), x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); break; }
//这个performButtonActionOnTouchDown(event) 一般的设备都是返回false.因为目前的实现中,它是处理如鼠标的右键的.(如果此View响应或者其父View响应右键菜单,那么就此事件就被消耗掉了.) 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; //将view的状态变为PREPRESSED,检测是Tap还是长按事件 if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } //如果是在滚动器当中,在滚动器当中的话延迟返回事件,延迟时间为 ViewConfiguration.getTapTimeout()=100毫秒 //在给定的tapTimeout时间之内,用户的触摸没有移动,就当作用户是想点击,而不是滑动. 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( ViewConfiguration.getLongPressTimeout(), x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } break;
//接收到系统发出的ACTION_CANCLE事件时,重置状态, 将所有的状态设置为最初始 case MotionEvent.ACTION_CANCEL: if (clickable) { setPressed(false); } removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; break; //如果是移动的手势 case MotionEvent.ACTION_MOVE: if (clickable) { //将实时位置传递给背景(前景)图片 drawableHotspotChanged(x, y); }
finalint motionClassification = event.getClassification(); finalboolean ambiguousGesture = motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; int touchSlop = mTouchSlop; if (ambiguousGesture && hasPendingLongPressCallback()) { finalfloat ambiguousMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); // 判断当前滑动事件是否还在当前view当中,不是就执行下面的方法 if (!pointInView(x, y, touchSlop)) { // The default action here is to cancel long press. But instead, we // just extend the timeout here, in case the classification // stays ambiguous. removeLongPressCallback(); long delay = (long) (ViewConfiguration.getLongPressTimeout() * ambiguousMultiplier); // Subtract the time already spent delay -= event.getEventTime() - event.getDownTime(); checkForLongClick( delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } touchSlop *= ambiguousMultiplier; }
// Be lenient about moving outside of buttons if (!pointInView(x, y, touchSlop)) { // Outside button // Remove any future long press/tap checks // 移除PREPRESSED状态和对应回调 removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // 是PRESSED就移除长按检测,并移除PRESSED状态 setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; }
finalboolean deepPress = motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; if (deepPress && hasPendingLongPressCallback()) { // process the long click action immediately removeLongPressCallback(); checkForLongClick( 0/* send immediately */, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS); }
break; }
returntrue; }
returnfalse; }
① 源码虽然看起来很长,但是我们先关注 我们想要的 onClick方法 的实现。乍一看,似乎没有看到,但是我们关注代码的 114~122。也就是下面的代码
1 2 3 4 5 6 7 8 9
//UI子线程去执行click,为了让click事件开始的时候其他视觉发生变化不影响。 if (mPerformClick == null) { mPerformClick = new PerformClick(); }
//如果post消息失败,直接调用处理click事件 if (!post(mPerformClick)) { performClickInternal(); }
② 看起来我们的 onClick方法 就在其中,而观察下面 PerformClick类源码 的代码。我们发现似乎关注的焦点到了 performClickInternal方法 上。
③ 我们进入 performClickInternal方法 的源码,发现执行了 performClick 方法。
1 2 3 4 5 6 7 8 9
privatebooleanperformClickInternal(){ // Must notify autofill manager before performing the click actions to avoid scenarios where // the app has a click listener that changes the state of views the autofill service might // be interested on. notifyAutofillManagerOnClick();
/** * Call this view's OnClickListener, if it is defined. Performs all normal * actions associated with clicking: reporting accessibility event, playing * a sound, etc. * * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. */ // NOTE: other methods on View should not call this method directly, but performClickInternal() // instead, to guarantee that the autofill manager is notified when necessary (as subclasses // could extend this method without calling super.performClick()). publicbooleanperformClick(){ // We still need to call this method to handle the cases where performClick() was called // externally, instead of through performClickInternal() notifyAutofillManagerOnClick();
finalboolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; }