Android事件分发解析

164 阅读14分钟

1. Android事件分发简介

  • Android事件分发一般是指从手指触摸屏幕产生触摸事件到视图响应事件所经历的过程
  • 事件分发所涉及到的偏底层的InputManager之类的在此就不做深入探讨了,着重分析下从Activity开始的偏上层的分析
  • 事件分发主要涉及Activity->ViewGroup->...->ViewGroup->View之间的事件传递以及事件处理
  • 事件分发涉及到的三个比较重要的方法为事件分发dispatchTouchEvent、事件拦截onInterceptTouchEvent以及事件处理onTouchEvent,接下来就从Activity的dispatchTouchEvent开始逐步分析

2. Activity.dispatchTouchEvent

  • 事件产生后首先是传递给Activity的dispatchTouchEvent方法,直接看下Activity源码中的dispatchTouchEvent()的源码
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // 这是一个空方法,供开发者重写
        // 在我们开始与Activity进行交互的时候被调用,常重写用来取消屏保等操作
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
  • 这里的代码很简单,直接继续看关键代码getWindow().superDispatchTouchEvent(ev),其中getWindow()返回的是Activity的成员变量mWindow,而这个mWindow的类型是Window,Window只是一个抽象类,具体mWindow的赋值是在attach()中,将mWindow赋值给Window的唯一实现类PhoneWindow的实例对象
 // Activity.java
// getWindow返回Activity的成员变量mWindow
public Window getWindow() {
    return mWindow;
}

// mWindow是在Activity.attach中赋值的
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    attachBaseContext(context);
...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
}
  • 所以根据以上追踪superDispatchTouchEvent(ev)应该追溯到PhoneWindow类中,最终调用的是 mDecor.superDispatchTouchEvent(event),mDecor是PhoneWindow的一个DecorView类型的成员变量,所以最终追溯到DecorView中的superDispatchTouchEvent(),这个方法最终是调用的super.dispatchTouchEvent(event)即父类的dispatchTouchEvent方法,而DecorView继承自FrameLayout,FrameLayout是继承父类ViewGroup的dispatchTouchEvent,所以最终是调用的ViewGroup.dispatchTouchEvent(event),自此事件分发dispatchTouchEvent完成了由Activity到ViewGroup的流转
// PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

// DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

3. ViewGroup.dispatchTouchEvent

3.1 onInterceptTouchEvent拦截

  • 先来看ViewGroup.dispatchTouchEvent中判断是否拦截相关代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...重置相关代码...
    // 检查是否拦截事件
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        // 使用requestDisallowInterceptTouchEvent(boolean disallowIntercept)
        // 也可修改FLAG_DISALLOW_INTERCEPT标志位从而影响父元素是否拦截
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false;
        }
    } else {
        // There are no touch targets and this action is not an initial down
        // so this view group continues to intercept touches.
        intercepted = true;
    }
    ...子view处理相关...
}
  • 从代码可以看出,只有事件类型为按下MotionEvent.ACTION_DOWN时或者mFirstTouchTarget != null时才会去判断是否拦截,MotionEvent.ACTION_DOWN就表示按下事件比较好理解,这个mFirstTouchTarget是一个全局变量,从后面的代码逻辑可以看出,他是在ViewGroup.addTouchTarget()方法中被赋值的,而当事件由ViewGroup子元素成功处理时,这个方法会被调用,mFirstTouchTarget会被赋值给该子元素
  • 当ViewGroup不拦截事件,并将事件交由子元素处理时,mFirstTouchTarget != null便成立;反之,当ViewGroup拦截事件时,mFirstTouchTarget != null便不成立,拦截后当ACTION_MOVE和ACTION_UP来临时,actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null这两个条件,便均不成立,这也就会导致ViewGroup.onInterceptTouchEvent()不会被调用,并且后续的ACTION_MOVE和ACTION_UP等一系列事件都会默认交由他本身处理
  • 拦截事件中还有一个特殊的标记FLAG_DISALLOW_INTERCEPT,这个标记也可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)这个方法来设置,一般提供给子View中调用,设置后,ViewGroup将无法拦截除ACTION_DOWN以外的事件,之所以说是除ACTION_DOWN以外的事件是由于事件分发在ACTION_DOWN时会调用resetTouchState重置FLAG_DISALLOW_INTERCEPT标志位

3.2 分发子View处理

  • 对于每一个down事件(包括ACTION_DOWN和ACTION_POINTER_DOWN)ViewGroup都会优先在控件树中寻找合适的子控件来消费。因为对于每一个down事件,标志着一个触控点的一个崭新的事件序列,viewGroup会寻找合适的子控件。如果找不到合适的子控件,才会自己处理down事件。因为消费了down事件,意味着接下来该触控点的事件序列事件都会交给该view消费,如果viewGroup拦截了事件,那么子view就无法接收到任何事件消息。
  • ViewGroup寻找子控件的规则是view绘制顺序的逆序,即Z轴系数越高,显示越外层,越先被分发。下面看看相关的源码
---------ViewGroup.java------Api30----------
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...重置相关代码...
    ...拦截相关代码...

    final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
            && !isMouseEvent;
    // 如果是down或pointer_down事件,则对应新的事件序列的TouchTarget
    TouchTarget newTouchTarget = null;
    // 事件是否已经分发给targetView了
    boolean alreadyDispatchedToNewTouchTarget = false;
    // 事件未被取消和拦截才进入分发逻辑
    if (!canceled && !intercepted) {
        ...
        // down或pointer_down事件,表示新的手指按下了,需要寻找接收事件的view
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            final int actionIndex = ev.getActionIndex(); // always 0 for down
            final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                    : TouchTarget.ALL_POINTER_IDS;

            // Clean up earlier touch targets for this pointer id in case they
            // have become out of sync.
            removePointersFromTouchTargets(idBitsToAssign);

            final int childrenCount = mChildrenCount;
            // 如果子控件数目不为0且事件还没绑定到新的子View
            if (newTouchTarget == null && childrenCount != 0) {
                final float x =
                        isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                final float y =
                        isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                // 从前到后(外到里)创建View列表
                final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                final boolean customOrder = preorderedList == null
                        && isChildrenDrawingOrderEnabled();
                final View[] children = mChildren;
                // 遍历所有子View
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final int childIndex = getAndVerifyPreorderedIndex(
                            childrenCount, i, customOrder);
                    final View child = getAndVerifyPreorderedView(
                            preorderedList, children, childIndex);
                    // 检查该子view是否可以接受触摸事件和是否在点击的范围内
                    if (!child.canReceivePointerEvents()
                            || !isTransformedTouchPointInView(x, y, child, null)) {
                        continue;
                    }

                    // 检查该子view是否在touchTarget链表中
                    newTouchTarget = getTouchTarget(child);
                    if (newTouchTarget != null) {
                        // 链表中已经存在该子view,说明这是一个多点触摸事件,即两次都触摸到同一个view上
                        // 将新的触控点id绑定到该TouchTarget上
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                        break;
                    }

                    resetCancelNextUpFlag(child);
                    // 找到合适的子view,把事件分发给他,看该子view是否消费了down事件
                    // 如果消费了,需要生成新的TouchTarget
                    // 如果没有消费,说明子view不接受该down事件,继续循环寻找合适的子控件
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        // Child wants to receive touch within its bounds.
                        mLastTouchDownTime = ev.getDownTime();
                        if (preorderedList != null) {
                            // childIndex points into presorted list, find original index
                            for (int j = 0; j < childrenCount; j++) {
                                if (children[childIndex] == mChildren[j]) {
                                    mLastTouchDownIndex = j;
                                    break;
                                }
                            }
                        } else {
                            mLastTouchDownIndex = childIndex;
                        }
                        mLastTouchDownX = ev.getX();
                        mLastTouchDownY = ev.getY();
                        // 保存该view到target链表
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        // 标记已经分发给子view,退出循环
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }

                    // The accessibility focus didn't handle the event, so clear
                    // the flag and do a normal dispatch to all children.
                    ev.setTargetAccessibilityFocus(false);
                }
                if (preorderedList != null) preorderedList.clear();
            }

            if (newTouchTarget == null && mFirstTouchTarget != null) {
                // Did not find a child to receive the event.
                // Assign the pointer to the least recently added target.
                newTouchTarget = mFirstTouchTarget;
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                }
                newTouchTarget.pointerIdBits |= idBitsToAssign;
            }
        }
    }
    ...派发事件相关代码...
}
  • 看一下关键的dispatchTransformedTouchEvent方法,这里涉及到关键的事件方法逻辑
---------ViewGroup.java------Api30----------
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                              View child, int desiredPointerIdBits) {
    final boolean handled;

    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    final int oldAction = event.getAction();
    // 如果是取消事件,那么不需要做其他额外的操作,直接派发事件即可,然后直接返回
    // 因为对于取消事件最重要的内容就是事件本身,无需对事件的内容进行设置
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    // If for some reason we ended up in an inconsistent state where it looks like we
    // might produce a motion event with no pointers in it, then drop the event.
    if (newPointerIdBits == 0) {
        return false;
    }

    // If the number of pointers is the same and we don't need to perform any fancy
    // irreversible transformations, then we can reuse the motion event for this
    // dispatch as long as we are careful to revert any changes we make.
    // Otherwise we need to make a copy.
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);

                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }

    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
        // 调用子view的方法进行分发
        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // 分发完毕,回收MotionEvent
    transformedEvent.recycle();
    return handled;
}
  • 接着看dispatchTouchEvent方法最后剩余的派发事件相关代码
---------ViewGroup.java------Api30----------
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...重置相关代码...
    ...拦截相关代码...

    if (onFilterTouchEventForSecurity(ev)) {
      ...分发相关代码...
        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // 经过了前面的处理,到这里touchTarget依旧为null,说明没有找到处理down事件的子控件
            // 或者down事件被viewGroup本身消费了,所以该事件由viewGroup自己处理
            // 这里调用了dispatchTransformedTouchEvent方法来分发事件
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // 已经有子View消费了down事件
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            // 遍历所有的TouchTarget并把事件分发下去
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    // 表示事件在前面已经处理了,不需要重复处理
                    handled = true;
                } else {
                    // 正常分发事件或者分发取消事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    // 调用dispatchTransformedTouchEvent方法来分发事件
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    // 如果发送了取消事件,则移除该target
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // 如果接收到取消或者up事件,说明事件序列结束,直接删除所有的TouchTarg
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            // 清除记录的信息
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            // 如果仅仅只是一个PONITER_UP
            // 清除对应触控点的触摸信息
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}
  • 每一个触控点的事件序列(down到up),只能给一个view消费;如果一个view消费了一个触控点的down事件,那么该触控点的后续事件都会给他处理
  • 每一个事件到达viewGroup,如果需要分发到子view,那么viewGroup首先会判断是否需要拦截
    • 当viewGroup的touchTarget!=null || 事件的类型为down 需要进行判断是否拦截;
    • 判断是否拦截受两个因素影响:onInterceptTouchEvent和FLAG_DISALLOW_INTERCEPT(对应requestDisallowInterceptTouchEvent)标志
  • 如果事件是down类型,那么需要遍历所有的子控件判断是否有子控件消费该down事件
  • 根据前面的处理情况,将事件派发到viewGroup自身或touchTarget中,如果touchTarget==null,说明没有子控件消费了down事件,那么viewGroup自己处理事件

4. View.dispatchTouchEvent

---------View.java-------Api30---------
public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false;

    final int actionMasked = event.getActionMasked();
    // 如果是down事件,停止嵌套滑动
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();
    }

    // 安全过滤,窗口位于非全屏窗口之下时,可能会阻止控件处理触摸事件
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            // 如果事件为鼠标拖动滚动条
            result = true;
        }

        ListenerInfo li = mListenerInfo;
        // 先判断mListenerInfo是否为空,即是否设置了setOnTouchListener
        // 设置了之后会首先回调onTouch方法,假如onTouch返回true,result=true,
        // 下面的&&就会阻止onTouchEvent方法的执行
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        // 设置了setOnTouchListener并且onTouch回调方法返回true,onTouchEvent就不会执行
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // 如果是事件序列终止事件或者没有消费down事件,终止嵌套滑动
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}
  • 源码比较简单,主要对事件的处理涉及到了onTouchListener监听器的onTouch方法以及本身的onTouchEvent方法
  • 当设置有onTouchListener监听器即调用了setOnTouchListener方法便首先执行onTouchListener接口的抽象方法onTouch,onTouch返回值为false时则会继续回调本身的onTouchEvent方法,onTouch返回值为true时onTouchEvent方法将不会执行
  • onTouchEvent方法的默认内部实现也包含了点击、长按等逻辑的处理,接着看下onTouchEvent部分的源码
----------View.java-------Api30-----------------
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;

    // 一个被禁用的view如果被设置为clickable,那么他仍旧是可以消费事件的
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        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)) {
            return true;
        }
    }

    // 如果该控件是可点击的,或者长按会出现工具提示
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    // 如果是长按显示工具类标志,回调该方法
                    handleTooltipUp();
                }
                if (!clickable) {
                    // 如果是不可点击的view,会清除所有的标志,恢复状态
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                // 判断是否是按下状态
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // 如果可以获取焦点但是没有获得焦点,请求获取焦点
                    // 正常的触摸模式下是不需要获取焦点,例如我们的button
                    // 但是如果在按键模式下,需要先移动光标选中按钮,也就是获取focus
                    // 再点击确认触摸按钮事件
                    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);
                    }

                    // mHasPerformedLongPress:长按事件是否已经响应
                    // mIgnoreNextUpEvent:是否忽略本次up事件
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check
                        // 这是一个单击事件,还没到达长按的时间,移除长按标志
                        removeLongPressCallback();

                        // 只有不能获取焦点的控件才能触摸click监听
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            // 这里使用发送到消息队列的方式而不是立即执行onClickListener
                            // 原因在于可以在点击前触发一些其他视觉效果
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }

                    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();
                    }

                    // 移除单击标志
                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                // 长按标志
                mHasPerformedLongPress = false;

                // 如果是不可点击的view,说明是长按提示工具的view
                // 直接检查是否发生了长按
                if (!clickable) {
                    // 这个方法会发送一个延迟的任务
                    // 如果延迟时间到还是按下状态,那么就会回调onLongClickListener接口
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    break;
                }

                if (performButtonActionOnTouchDown(event)) {
                    break;
                }

                // 向上遍历view查看是否在一个可滑动的容器中
                boolean isInScrollingContainer = isInScrollingContainer();

                // 如果在一个可滑动的容器中,那么需要延迟一小会再响应反馈
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    // 利用消息队列来延迟检测一个单击事件,延迟时间是ViewConfiguration.getTapTimeout()
                    // 这个时间是100ms
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // 没有在可滑动的容器中,直接响应触摸反馈
                    // 设置按下状态为true
                    setPressed(true, x, y);
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                }
                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) {
                    drawableHotspotChanged(x, y);
                }

                final int motionClassification = event.getClassification();
                final boolean ambiguousGesture =
                        motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
                int touchSlop = mTouchSlop;
                // view已经被设置了长按标志且目前的事件标志是模糊标志
                // 系统并不知道用户的意图,所以即使滑出了view的范围,并不会取消长按标志
                // 而是延长越界的误差范围和检查长按的时间
                // 因为这个时候系统并不知道你是想要长按还是要滑动,结果就是两种行为都没有响应
                // 由你接下来的行为决定
                if (ambiguousGesture && hasPendingLongPressCallback()) {
                    // 判断此时触控点的位置是否还在view的范围内
                    // touchSlop是一个小范围的误差,超出view位置slop距离依旧判定为在view范围内
                    if (!pointInView(x, y, touchSlop)) {
                        // 移除原来的长按标志
                        removeLongPressCallback();
                        // 延长等待时间,这里是原来长按等待的两倍
                        long delay = (long) (ViewConfiguration.getLongPressTimeout()
                                * mAmbiguousGestureMultiplier);
                        // 减去已经等待的时间
                        delay -= event.getEventTime() - event.getDownTime();
                        // 添加新的长按标志
                        checkForLongClick(
                                delay,
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    touchSlop *= mAmbiguousGestureMultiplier;
                }

                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, touchSlop)) {
                    // 如果已经超出范围,直接移除点击标志和长按标志,点击和长按事件均无法响应
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                }

                final boolean deepPress =
                        motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                // 表示用户在屏幕上用力按压,加快长按响应速度
                if (deepPress && hasPendingLongPressCallback()) {
                    // 移除原来的长按标志,直接响应长按事件
                    removeLongPressCallback();
                    checkForLongClick(
                            0 /* 延迟时间为0直接触发 */,
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                }

                break;
        }

        return true;
    }

    return false;
}
  • onClick是在onTouchEvent中的ACTION_UP事件是才可能触发,而onLongClick则是在ACTION_DOWN或者ACTION_MOVE事件时才可能触发
  • 当View/ViewGroup的clickable属性为true时,无论是否是enable,它都将消费事件(onTouchEvent)

5. 事件分发的总结

image.png

  • 事件分发主要分三块:分发(dispatchTouchEvent)、拦截(ViewGroup独有的onInterceptTouchEvent)以及处理(onTouchEvent);当我们触摸到屏幕产生点击事件后,首先传递给Activity的dispatchTouchEvent方法,方法会调用getWindow().superDispatchTouchEvent(ev)通过PhoneWindow传递给DecorView,然后再传递给根ViewGroup,进入ViewGroup的dispatchTouchEvent事件的分发方法,然后执行事件拦截方法onInterceptTouchEvent,在不拦截的情况下,此时会遍历ViewGroup的子元素,进入子元素的dispatchToucnEvent方法,然后执行view的onTouchEvent方法,如果是返回false不消费的话再接着走ViewGroup的消费事件onTouchEvent,如果ViewGroup也不消费即返回false,最后会回到Activity的消费事件onTouchEvent。

  • 参考:juejin.cn/post/692123… 事件分发系列文章