事件分发

触摸事件类型

触摸事件对应的类是 MotionEvent 类,事件的主要类型有以下四种:

  • ACTION_DOWN 手指刚接触屏幕
  • ACTION_MOVE 手指在屏幕上滑动
  • ACTION_UP 手指从屏幕上抬起
  • ACTION_CANCEL 非人为因素取消ß

View 事件分发的本质就是将 MotionEvent 类分发给具体某个View 处理的过程,一次完整的事件响应包含 一个 ACTION_DOWN 事件,中间任意个 ACTION_MOVE 事件,以及最后的 ACTION_UP 事件或者 ACTION_CANCEL 事件。

其中 ACTION_CANCEL 事件的触发会在以下两种情况下发生:

  1. 如果在父View中拦截ACTION_UP或ACTION_MOVE,在第一次父视图拦截消息的瞬间,父视图指定子视图不接受后续消息了,同时子视图会收到ACTION_CANCEL事件。
  2. 如果触摸某个控件,但是又不是在这个控件的区域上抬起(移动到别的地方了),就会出现action_cancel。

事件分发的对象

具备事件分发能力的对象有以下三种:

  • Activity 控制当前页面的生命周期,以及事件分发的第一层View
  • ViewGroup 一组View的集合,包含多个 View 或者 ViewGroup
  • View 所有 UI 组件的基类

事件分发的大致流程:当手指触碰到屏幕时,系统底层会发出一个信号,最终到达 Activity,由Activity向下把事件分给下层的ViewGroup,再由下层的ViewGroup向下分发,直到找到可以处理当前事件的视图,并把后续事件也交给其处理。

事件分发的方法

事件分发的过程由三个主要的方法完成:

  1. dispatchTouchEvent:分发。返回 true 表示事件被当前 View 消费掉;返回 false 表示交给父类去处理,返回 super.dispatchTouchEvent 表示继续分发下去。
  2. onInterceptTouchEvent:判断事件是否拦截(只存在于ViewGroup中)
  3. onTouchEvent:事件的处理

事件分发流程

Activity 中的 分发过程

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
		// 交给 DecorView 去分发事件
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
		// 如果 DecorView 没有处理这个事件,将事件交给自己处理
    return onTouchEvent(ev);
}

// 
public boolean onTouchEvent(MotionEvent event) {
		// 针对 类似 DialogActivity,点击了外部区域,关闭当前的 Activity
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }

    return false;
}

ViewGroup中的事件分发

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;
		// 
    if (onFilterTouchEventForSecurity(ev)) {
				...
        // 判断是否需要拦截这个事件
        final boolean intercepted;
				// DOWN 事件,或者已经有相应视图处理过之前的事件
				// mFirstTouchTarget 主要是用来记录 从 顶层容器 到 处理这个 事件的 一个View 的链路
				// 第二次的事件分发 就直接走这个 链路进行分发
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            
						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 {
            // 没有找到处理过事件的视图,并且也不是 down 事件
            intercepted = true;
        }

        ...
        if (!canceled && !intercepted) {
						...
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
								...
								// 判断哪些子View可以接收这个的事件分发
             }
				}

        // 创建 事件分发的链表
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // Dispatch to touch targets, excluding the new touch target if we already
            // dispatched to it.  Cancel touch targets if necessary.
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }
    }
    return handled;
}

final int childrenCount = mChildrenCount;
      if (newTouchTarget == null && childrenCount != 0) {
            final float x = ev.getX(actionIndex);
            final float y = ev.getY(actionIndex);
            // 找到能处理这个 Event 的 子View 
            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
            final boolean customOrder = preorderedList == null
                    && isChildrenDrawingOrderEnabled();
            final View[] children = mChildren;
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = getAndVerifyPreorderedIndex(
                        childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(
                        preorderedList, children, childIndex);
								// 判断这个子View 是否能处理这个 Event
                if (!canViewReceivePointerEvents(child)
                        || !isTransformedTouchPointInView(x, y, child, null)) {
                    ev.setTargetAccessibilityFocus(false);
                    continue;
                }

                newTouchTarget = getTouchTarget(child);
                if (newTouchTarget != null) {
                    // Child is already receiving touch within its bounds.
                    // Give it the new pointer in addition to the ones it is handling.
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                    break;
                }

                resetCancelNextUpFlag(child);
								// 往子View做分发
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    ...
										// 搭建事件分发处理链表
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    
                    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;
        }
    }
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    final boolean handled;
		...
    if (child == null) {
				// 如果已经没有子View 了,调用父类的 分发方法,这里一般就直接调用了 View 的分发方法
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
				...
				// 传给子View 去进行分发
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    return handled;
}

View 中的事件分发

public boolean dispatchTouchEvent(MotionEvent event) {

    boolean result = false;
		// 当前 View 是否可见 或者是否被遮挡
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }

        ListenerInfo li = mListenerInfo;
				// 优先响应 onTouchListener 事件
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
				// onTouchListener 事件 返回 false,响应自身的 onTouchEvent 事件
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    return result;
}

public boolean onTouchEvent(MotionEvent event) {
		// 判断当前View 是否可点击 可长按,如果 可点击可长按的话,最终一定 return true
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
		// 有设置代理走 代理方法
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
				...
				return true;
    }

    return false;
}

ACTION_DOWN

mHasPerformedLongPress = false;

if (!clickable) {
    checkForLongClick(0, x, y);
    break;
}

// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();

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, x, y);
}

// 通过 Handler 发送一个 500ms 的延迟消息,在 CheckForLongPress run 方法中将 
// mHasPerformedLongPress 标志位设置为 ture 代表长按方法已经执行
private void checkForLongClick(int delayOffset, float x, float y) {
    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
        mHasPerformedLongPress = false;

        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
        }
        mPendingCheckForLongPress.setAnchor(x, y);
        mPendingCheckForLongPress.rememberWindowAttachCount();
        mPendingCheckForLongPress.rememberPressedState();
        postDelayed(mPendingCheckForLongPress,
                ViewConfiguration.getLongPressTimeout() - delayOffset);
    }
}

ACTION_CANCEL

if (clickable) {
    setPressed(false);
}
// 移除 DOWN 事件时,通过Handler发出的消息,不在触发 LongClick
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;

ACTION_MOVE

if (clickable) {
    drawableHotspotChanged(x, y);
}

// 滑动到外部去了 
if (!pointInView(x, y, mTouchSlop)) {
    // 移除 DOWN 事件时,通过Handler发出的消息,不在触发 LongClick
    removeTapCallback();
    removeLongPressCallback();
    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
        setPressed(false);
    }
    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}

ACTION_UP

boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
		// 如果长按事件没有响应,这个时候来执行点击事件
    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
        // 移除 DOWN 事件时,通过Handler发出的消息,不在触发 LongClick
        removeLongPressCallback();
        if (!focusTaken) {

            if (mPerformClick == null) {
                mPerformClick = new PerformClick();
            }
            if (!post(mPerformClick)) {
                performClickInternal();
            }
        }
    }
    removeTapCallback();
}

事件分发的整体模型

滑动事件冲突

外部拦截

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/789a1c01-3584-4986-a2e6-a7f157d0bfda/Untitled.png

内部拦截

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/868f6ae1-fcdd-4f4c-b985-27c5e40516e9/Untitled.png