背景
在安卓事件分发学习之onInterceptTouchEvent方法一文中,我记录了事件分发流程里第二个方法onInterceptTouchEvent()的源码阅读过程,现在记录一下最后一个方法onTouchEvent()的阅读
在文章安卓事件分发学习之dispatchTouchEvent方法中,可以看到onTouchEvent()方法的执行优先级是View->ViewGroup->Activity,那我就按着这个顺序来阅读此方法
其实在阅读的时候发现,ViewGroup并没有覆写onTouchEvent,所以它执行的,还是View类的onTouchEvent。
所以只看View.onTouchEvent()和Activity.onTouchEvent()就行
View#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(); final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; // 是不是可点击的 if ((viewFlags & ENABLED_MASK) == DISABLED) { // 如果当前view是disable状态 if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); // action_up事件,并且pressed标志位不是0,就设置pressed为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; // 如果一个view是disable但可点击,就仍然消费这个事件流,但不响应 } // 设置touch代理的话,调用touch代理的onTouchEvent(),并且返回true,确定消费事件流 if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { // 只考虑clickable的情况,不是clickable,绝对返回false switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } if (!clickable) { // 移除pressed标志位和mPendingCheckForTap回调,但是一般情况这个!clickable判断不成立 removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; // 是否设置了prePressed或是否按下pressed 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()) { // 当前view或子view是否有焦点,有的话,不会执行onClick() focusTaken = requestFocus(); } if (prepressed) { setPressed(true, x, y); // action_up的时候才会setPressed() } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // 没有执行onLongClick()并且不是多点触摸的话(mIgnoreNextUpEvent似乎在多点触摸的情况下,才可能为true) // This is a tap, so remove the longpress check removeLongPressCallback(); // 移除监听 // Only perform take click actions if we were in the pressed state if (!focusTaken) { // 当前view或子view没有焦点,才会执行onClick() if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { // action_up的时候才会调用onClick() performClick(); } } } // UnsetPressedState类唯一的方法--run()的唯一操作就是setPressed(false) // 这里的目的当然就是设置pressed为false if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } // 移除pressed标志位和mPendingCheckForTap回调 removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { mPrivateFlags3 |= PFLAG3_FINGER_DOWN; } mHasPerformedLongPress = false; if (!clickable) { // 当前view不是clickable,就尝试执行onLongClick() checkForLongClick(0, x, y); // 但一般情况下,这个判断不会成立 break; } if (performButtonActionOnTouchDown(event)) { // 这个方法一般返回false break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // 三大布局这里都是false // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. // 根据父view能不能scroll决定是不是尝试执行onLongClick if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // 一般是走这儿 setPressed(true, x, y); checkForLongClick(0, x, y); } break; 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) { // 当前view可点击的话,重新画热区 drawableHotspotChanged(x, y); } // Be lenient about moving outside of buttons // 如果移到了view外面,进行回调的移除和标志位的回置,setPressed为false if (!pointInView(x, y, mTouchSlop)) { // Outside button // Remove any future long press/tap checks removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } break; } return true; } return false; }
代码虽长,逻辑却算不得复杂
得出来的最重要的几个结论就是:
1、onClick、onLongClick优先级是小于onTouchEvent()的,同时结合前一篇文章,可以得到优先级如下:onTouch()、onTouchEvent()、onLongClick()、onClick()
2、onClick是在action_up里执行的,onLongClick是在action_down里执行的。而且如果onLongClick返回true,onClick不会执行
3、有焦点的时候是不会执行onClick的
可以参加下面的源码记录
longClick
在action_down里面,调用了一个重要方法:checkForLongClick,源码如下
private void checkForLongClick(int delayOffset, float x, float y) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) { mHasPerformedLongPress = false; // 这个标志位决定了onClick是否在action_up时调用 if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); mPendingCheckForLongPress.rememberPressedState(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); } }
如果是long_clickable的话,就post一个runnable类对象--CheckForLongPress类,此类源码如下
private final class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; private float mX; private float mY; private boolean mOriginalPressedState; @Override public void run() { if ((mOriginalPressedState == isPressed()) && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } } } public void setAnchor(float x, float y) { mX = x; mY = y; } public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } public void rememberPressedState() { mOriginalPressedState = isPressed(); } }
最主要的是run()方法,正常情况下,run()方法里第一个判断会成立,那就会执行performLongClick(),它的返回值为true,mHasPerformLongPress就为true,反之为false
performLongClick()方法直接调用了performLongClickInternal()方法,源码如下
private boolean performLongClickInternal(float x, float y) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnLongClickListener != null) { handled = li.mOnLongClickListener.onLongClick(View.this); } if (!handled) { final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y); handled = isAnchored ? showContextMenu(x, y) : showContextMenu(); } if ((mViewFlags & TOOLTIP) == TOOLTIP) { if (!handled) { handled = showLongClickTooltip((int) x, (int) y); } } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return handled; }
可见,最主要的就是我们的监听器的onLongClick()方法,它为true,就铁定返回true,onClick()就不会执行,它为false,一般情况下(没考虑上下文菜单),onClick才会执行
Activity#onTouchEvent
如果一个事件流没有一个view处理,那就会走到Activity.onTouchEvent()中,这个方法代码如下
public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { /* public boolean shouldCloseOnTouch(Context context, MotionEvent event) { if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) && peekDecorView() != null) { return true; // 超过了边界而且是down事件,返回true } return false; } */ finish(); return true; } return false; }
这里返回true或false已经无所谓了,反正事件流也到头了。
结语
安卓事件分发源码大体就这样了,源码来自sdk-26